几种常见排序
1. 归并排序【图来自归并排序】
function merge(leftArr,rightArr) {
let resultArr = [];
let left=0;
let right = 0;
while(left!=leftArr.length && right!=rightArr.length){
if(leftArr[left]<rightArr[right]){
resultArr.push(leftArr[left]);
left++;
}else {
resultArr.push(rightArr[right]);
right++;
}
}
if(left==leftArr.length){
return resultArr.concat(rightArr.slice(right,rightArr.length));
}
return resultArr.concat(leftArr.slice(left,leftArr.length));
}
function mergeSort(arr,start,end) {
if(start==end){
return arr.slice(start,start+1);
}
let mid = Math.floor(start+(end-start)/2);
let leftArr = mergeSort(arr,start,mid);
let rightArr = mergeSort(arr,mid+1,end);
return merge(leftArr,rightArr);
}
function sort(arr) {
return mergeSort(arr,0,arr.length-1)
}
console.log(sort([5,7,2,1,8,4,3,0,10]))
2. 希尔排序【图源希尔排序】
function swap(arr,a,b) {
let temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
function sortAnthor(arr) {
for(let gap = Math.floor(arr.length/2);gap>0;gap=Math.floor(gap/2)){
for(let i=gap;i<arr.length;i++){
let temp = arr[i];
let j=i;
while(j-gap>=0 && temp<arr[j-gap]){
arr[j] = arr[j-gap];
j-=gap;
}
arr[j] = temp;
// if(arr[j]<arr[j-gap]){
//
// }
}
}
return arr;
}
function sort(arr) {
for(let gap=Math.floor(arr.length/2);gap>0;gap=Math.floor(gap/2)){
//控制gap,直至为1
for(let i=gap;i<arr.length;i++){
let j = i;
while(j-gap>=0 && arr[j]<arr[j-gap]){
swap(arr,j,j-gap);
j-=gap;
}
}
}
return arr;
}
console.log(sort([5,7,2,1,8,4,3,0,10]))
3. 快速排序【快速排序】
function getHigh(arr,low,high) {
let temp = arr[low];
while(low<high){
while(low<high && arr[high]>=temp){
high--;
}
arr[low] = arr[high];
while(low<high && arr[low]<=temp){
low++;
}
arr[high] = arr[low];
}
arr[high] = temp;
return high;
}
//方法2
function quick2(arr,low,high) {
if(low<high){
let index= getHigh(arr,low,high);
quick2(arr,0,index-1);
quick2(arr,index+1,high);
}
return arr;
}
//方法1
function quick(arr) {
if(arr.length<=1){
return arr;
}
let low=0;
let high = arr.length-1;
let temp = arr[low];
let nowDir = "left";
while(high!=low){
if(nowDir=="left"){
if(arr[high]<temp){
arr[low]=arr[high];
low++;
nowDir="right";
}else {
high--;
}
}else {
if(arr[low]>temp){
arr[high]=arr[low];
high--;
nowDir="left";
}else {
low++;
}
}
}
let mid = high;
let leftArr = quick(arr.slice(0,mid));
let rightArr = quick(arr.slice(mid+1,arr.length));
return leftArr.concat([temp],rightArr);
}
function quickSort(arr) {
return quick2(arr,0,arr.length-1)
}
console.log(quickSort([5,7,2,1,8,4,3,0,10]))
4. 插入排序
function insertSort(arr) {
let resultArr = [arr[0]];
for(let i=1;i<arr.length;i++){
for(let j=0;j<resultArr.length;j++){
if(arr[i]<resultArr[j]){
resultArr.splice(j,0,arr[i]);
break;
}
if(j==resultArr.length-1){
resultArr.push(arr[i]);
break;
}
}
}
return resultArr;
}
console.log(insertSort([5,7,2,1,8,4,3,0,10]))
5. 排序算法复杂度分析【图源以及参考文章:排序算法复杂度分析】
①基本概念
稳定性
- 所谓稳定性是指待排序的序列中有两元素相等,排序之后它们的先后顺序不变.假如为A1,A2.它们的索引分别为1,2.则排序之后A1,A2的索引仍然是1和2.
- 稳定也可以理解为一切皆在掌握中,元素的位置处在你在控制中.而不稳定算法有时就有点碰运气,随机的成分.当两元素相等时它们的位置在排序后可能仍然相同.但也可能不同.是未可知的.
- 另外要注意的是:算法思想的本身是独立于编程语言的,所以你写代码去实现算法的时候很多细节可以做不同的处理.采用不稳定算法不管你具体实现时怎么写代码,最终相同元素位置总是不确定的(可能位置没变也可能变了).而稳定排序算法是你在具体实现时如果细节方面处理的好就会是稳定的,但有些细节没处理得到的结果仍然是不稳定的.
- 比如冒泡排序,直接插入排序,归并排序虽然是稳定排序算法,但如果你实现时细节没处理好得出的结果也是不稳定的.
- 我们平时自己在使用排序算法时用的测试数据就是简单的一些数值本身.没有任何关联信息.这在实际应用中一般没太多用处.实际应该中肯定是排序的数值关联到了其他信息,比如数据库中一个表的主键排序,主键是有关联到其他信息.另外比如对英语字母排序,英语字母的数值关联到了字母这个有意义的信息.
- 可能大部分时候我们不用考虑算法的稳定性.两个元素相等位置是前是后不重要.但有些时候稳定性确实有用处.它体现了程序的健壮性.比如你网站上针对最热门的文章或啥音乐电影之类的进行排名.由于这里排名不会像我们成绩排名会有并列第几名之说.所以出现了元素相等时也会有先后之分.如果添加进新的元素之后又要重新排名了.之前并列名次的最好是依然保持先后顺序才比较好.
② 插入排序
- 当数组是正序时,插入排序的算法复杂度为O(n)。当数组为倒序时,插入排序算法的算法复杂度为O(n2)。在插入排序中,需要消耗一个空间来存储当前需要比较插入的元素。故其空间复杂度为O(1)。在经过插入排序后,值相等元素的相对位置不会改变,故其是稳定的。
③ 冒泡排序
- 当数组是正序时,冒泡排序的算法复杂度为O(n)(实际上原本是n2,但是可以通过优化,使其最好复杂度为O(n))。当数组是倒序时,冒泡排序的算法复杂度为O(n2)。在改进后的冒泡排序中,需要一个变量来存储是否在该扫描过程中存在交换(如果没有交换过,则说明该数组是正序)。故其空间复杂度为O(1)。冒泡排序不会改变值想等元素的相对位置,故其稳定的
//改进后的冒泡排序
public void bubbleSort(int arr[]) {
boolean didSwap;
for(int i = 0, len = arr.length; i < len - 1; i++) {
didSwap = false;
for(int j = 0; j < len - i - 1; j++) {
if(arr[j + 1] < arr[j]) {
swap(arr, j, j + 1);
didSwap = true;
}
}
if(didSwap == false)
return;
}
}
④ 选择排序
选择排序的最好最坏复杂度都为
o(n2)
(无论是正序还是倒序,每一趟都要将最小的值与每个元素进行比较,从而确定最小的值)。其需要一个指针指向最小的元素,故空间复杂度为o(1)。在选择排序中,假如有这样的序列:[3,4,5,3,2]
,最初最小的元素为3,当比较到最后一个元素时,最小元素为2,此时需要将2和第一个元素交换位置。故数组中的两个三的相对位置改变,故该排序算法是不稳定的
⑤ 快速排序
快速排序在最坏情况下退化成选择排序,故其最坏复杂度为
o(n2)
。最好情况下时间复杂度为o(nlogn)
。当数组为正序或者倒序时,通过快速排序会将其分为空数组和比传进来的数组少一个的数组,此时二叉树会退化成只有左孩子(右孩子)的单斜树。故会退化成选择排序。快速排序是不稳定的
⑥ 归并排序
归并排序最好最坏的情况的时间复杂度都为o(nlogn)。它需要的空间主要来源于临时数组的个数(n-1个),故其空间复杂度为o(n)。归并排序是稳定的。
⑦ 希尔排序
希尔排序是按照不同间隔对数组进行插入排序,故其时间复杂度是优于插入排序的。在最好情况下,即正序情况下,其需要比较(n-1)次,不需要后移。故希尔排序的最好的时间复杂度为
o(n)
。最坏情况下时间复杂度为0(n2)(但实际是优于o(n2)的)
还没有评论,来说两句吧...