莫名其妙的算法们:排序(一)

来源:互联网 发布:淘宝卖点文案 编辑:程序博客网 时间:2024/06/06 15:04

排序可是说是算法里面的经典问题了。排序算法的发展史,可以看作是算法进化史的一个缩影。

启蒙

在这一阶段的算法明确地提出了排序问题,并给出了简单易行的解决方案。虽然局限于奢侈的时间复杂度,但为后序的发展给出了良好的开端。

冒泡排序

冒泡排序几乎是每个初学者最早接触到的排序方案。其大体思想是每次遍历时,逐一比较相邻的两元素,将较大者交换至右侧。这样每次遍历都可以将当前序列的最大值交换至序列的最右端。不断进行以上的遍历方案,并在该过程中缩小待处理序列的规模,直至整个过程完成。

int* bubbleSort(int* A, int n) {    //长度为n的数组需要遍历n-1次    for(int i=0;i<n-1;++i){         //遍历开始时右侧已排好i个元素,故待处理区间为[0,n-i)        //因为需要j+1<n-i所以j的取值是[0,n-i-1)        for(int j=0;j<n-i-1;++j){            if(A[j]>A[j+1]){                swap(A[j],A[j+1]);            }        }    }    return A;}

一个简单的冒泡排序示意图:
这里写图片描述

选择排序

选择排序更容易理解一些。其大体思想是每次从待处理序列中选出最小者交换至最左端,然后对剩下的序列继续该操作直至排序结束。

int* selectionSort(int* A, int n) {    //长度为n的数组需要遍历n-1次    for(int i=0;i<n-1;++i){        //每次找出最小值的下标        int min=i;        //A[i]暂定为最小值,故j遍历范围是[i+1,n)        for(int j=i+1;j<n;++j){            if(A[j]<A[min])                min=j;        }        //交换        swap(A[min],A[i]);    }    return A;}

插入排序

插入排序很类似与打扑克牌时,把一张新摸到的牌插入到手牌里的处理方式。其大体思想是每次处理一个元素,该元素的左侧是已经处理好的有序数列(初始长度为1),将该元素插入到此数列的正确位置中去。

int* insertionSort(int* A, int n) {    //初始时认为A[0]是有序数列,待处理元素范围是[1,n)    for(int i=1;i<n;i++){        int get=A[i];        //j视作是待处理元素应插入数列中的位置,从i-1到0逆序搜索        int j=i-1;        //搜索条件:j未越界且位置j上的元素大于待处理元素        while(j>=0 && A[j]>get){            //相当于元素右移一位            A[j+1]=A[j];            j--;        }        //循环结束时,若j越界则需插入到A[0]处;        //否则A[j]<=get需要插入到A[j+1]处        A[j+1]=get;    }    return A;}

其他

除了上述之外还有一些改良算法,比如对冒泡排序改良的鸡尾酒排序,不过并未降低时间复杂度;对选择排序的双向选择排序,有轻微的时间复杂度改良;对插入排序的二分法插入排序,可以节省比较次数,但避免不了遍历次数。

总结与对比

时间复杂度:O(N2)
共需要N-1次循环,每次需要遍历N-…

成熟

成熟时期的排序算法可以达到O(NlogN)的时间复杂度,基本上已经是排序的极限了。尽管后序有O(N)的方案,但也仅仅在特定条件下有效。

快速排序

快速排序的思路不难想到,找出一个基准元素,小于它的放数组左边,大于它的放右边,然后对左右的子数组分别进行如上操作。其核心手段在于对数组进行调节,使得小于和大于基准元素的数据分布于它的异侧。
这个问题早期有这样一种解法:指针i和指针j分别指向首尾,并逐步向中间靠拢,指针i找到一个大于基准的元素,等待j指针找到一个小于基准的元素,两者交换。
不过现在比较流行的解法是下面这样的:给定一个小于等于区间(初始右边界为-1);当发现一个小于基准的元素,就把它与小于等于区间的右侧元素交换,同时该区间扩充一位。

归并排序

将两个有序数组合并是比较容易的,那么

原创粉丝点击