排序算法

来源:互联网 发布:淘宝9月什么时候有活动 编辑:程序博客网 时间:2024/06/05 09:26

1. 直接选择排序

任何情况下的时间复杂度都是 O(n^2), 最差的一种排序,每次都必须遍历玩整个子数组,选出最小的那个,空间复杂度为O(1),无需额外空间。直接选择排序的关键在于,每次从右侧子数组中,选出最小的,放到子数组第一个,然后缩小子数组范围,再次选择、交换。
#include <iostream>using namespace std;void selectionSort(int* a, int begin, int end){    if (begin >= end) return;     int min = a[begin];     for (int i = begin; i < end; i++)    {        if (a[i] < min)        {            // 不使用第三个数交换两个整型。            a[i] = a[i] ^ a[begin];             a[begin] = a[i] ^ a[begin];             a[i] = a[i] ^ a[begin];            min = a[begin];        }    }    selectionSort(a, ++begin, end);}int main(){    int n[7] = { 10, 10, 20, 1, 3, 8, 22 };    selectionSort(n, 0, 7);}

2. 直接插入排序

直接插入排序的平均复杂度为O(n^2),在最好的情况下,即基本有序时,复杂度为O(n).直接插入排序的关键在于,与直接选择相反,直接插入的操作序列是左侧的已排好序列,每次把右侧第一个值拿出来插到左侧已排好序列中。
    int* insertionSort(int* A, int n) {        for(int i = 1; i < n; i++)            for(int j = i; j > 0; j--)                if(A[j] < A[j - 1]) swap(A[j], A[j - 1]);        return A;    }

3. 希尔排序

希尔排序是直接插入排序的改良版,gap不再是1,而是动态变化,从n/2开始,每次除以2,直到1。利用了插入排序在基本有序的前提下效率最高的特点。
void shell_sort(int a[], int n){    for (int gap = n / 2; gap > 0; gap /= 2) // gap 从 n / 2 .. 1    {        // 下面是插入排序        for (int i = gap; i < n; i++)   // 遍历一遍        {// 步长不再是直接插入时的1,而是gap         // 前gap个([0] .. [gap - 1])是不需要检测的,因为是每组的第一个,所以i从gap开始            for (int j = i; j >= gap; j -= gap) // 注意这里 j-=gap            {                if (a[j] < a[j - gap]) swap(a[j - gap], a[j]);                else break;             }        }    }}

4. 直接交换(冒泡排序)

冒泡排序的平均时间复杂度为O(n^2),在最好情况下,即基本有序时,复杂度为O(n)。冒泡排序的关键是,每次冒泡上来一个当前子序列中的最大值(升序排列的话)。冒泡排序可以优化,比如下面,用exchange标记,如果某次冒泡没有交换,说明当时的子序列已经有序。

#include <iostream>using namespace std;void exchangeSort(int* n, int s){    // 优化冒泡排序    bool isexchange = false;     for (int i = 0; i < s; i++)    {        isexchange = false;        for(int j = 0; j < s - i - 1; j++)        {            if (n[j + 1] < n[j])            {                isexchange = true;                 n[j + 1] ^= n[j];                 n[j] ^= n[j + 1];                n[j + 1] ^= n[j];            }        }        if (isexchange == false) return;    }}int main(){    int n[7] = { 10, 10, 20, 1, 3, 8, 22 };    exchangeSort(n, sizeof(n)/ sizeof(int)); }

5.快速排序(加强版交换排序)

关键字:pivot 轴,partition() (划分)
快排有两种实现,分别应用于数组和单向链表,快排的关键是partition函数的实现 http://www.cnblogs.com/TenosDoIt/p/3665038.html
1. 定轴实现
对数组进行快排时使用这个方法。
快排采用了分治策略,平均时间复杂度为O(nlogn)。快排的关键在于选择基准,把子序列从左右两端,(如果是升序的话)左边找比基准大的,右边找比基准小的,交换两者,直到左右指针相遇。一个while嵌套两个查找while。快排在最坏情况下(逆序),复杂度为O(n^2)
partition 函数:
// 划分函数,返回pivot的最终位置int partition(int a[], int begin, int end){    // 选第一个作pivot即可    int pivot = a[begin];    int l = begin, r = end;     while (l < r)    {        // 从右往左找小的        while (l < r && a[r] >= pivot) --r;        a[l] = a[r];// 找到后把它放到低位,a[l]上肯定要么是a[begin]要么是大于pivot的,所以可以覆盖(提示:考虑下面这个循环退出的条件)        while (l < r && a[l] <= pivot) ++l;        a[r] = a[l];// 找到后把它放到高位,a[r]上肯定是小于pivot的,可以覆盖    }    // 此时 l 应该等于 r,这个是一个空位,可以覆盖,把pivot放到这里,左边的小于pivot,右边的大于pivot    a[l] = pivot;    return l;}
快排的另一种实现是挖坑实现
2. 挖坑法
对链表进行快排时使用这个方法
// 划分函数,返回pivot的最终位置int partition(int a[], int begin, int end){    int pivot = a[begin]; //选第一个作pivot    int low = begin; // 比pivot小的最后一个元素的下标    for (int i = begin + 1; i <= end; i++) // 比pivot小的放在左边        if (a[i] < pivot)            swap(a[i], a[++low]);//将此数与比pivot小的最后一个元素的下一个(比pivot大)交换    swap(a[begin], a[low]); // 将最后一个比pivot小的元素交换到头部    return low;}
两种实现的 quicksort入口都是一样的,
void quicksort(int a[], int begin, int end){    if (begin < end)    {        int pivot = partition(a, begin, end); // 使得总体有序,找到pivot的最终位置,        quicksort(a, begin, pivot - 1); // 使得左侧有序        quicksort(a, pivot + 1, end); // 使得右侧有序    }}

6. 归并排序

关键字:merge,() mergesort()
归并排序有两个函数,一个是用来合并两个已经排好的数组,并放到临时数组里,比较容易
// 将 begin .. end 排好序后放回a数组的 begin .. end 原位置// a[]的begin .. mid 与 mid + 1 .. end 子数组分别是两个已经排好序的数组// 需要借用一个辅助存储空间 temp, 这也是归并排序空间复杂度为O(n)的原因所在void merge(int a[], int begin, int mid, int end, int temp[]){    int l = begin, r = mid + 1;     int k = 0;     while (l <= mid && r <= end) // 当两个子数组都没用完时        temp[k++] = (a[l] < a[r] ? a[l++] : a[r++]);    while (l <= mid) // 当第一个没有用完时,用完它        temp[k++] = a[l++];     while (r <= end) // 当第二个没有用完时,用完它        temp[k++] = a[r++];    // 将temp中的内容拷贝回 a[]的begin, end位置    for (int i = begin; i <= end; i++)        a[i] = temp[i - begin]; }
然后是主调函数,是一个拆分函数
// 主调函数void mergesort(int a[], int begin, int end, int temp[]){    if (begin < end)    {        int mid = (begin + end) >> 1; // 取中点        mergesort(a, begin, mid, temp); // 另左边有序,临时数组temp可以多次使用        mergesort(a, mid + 1, end, temp); // 另右边有序        merge(a, begin, mid, end, temp); // 合并左右两个有序子数组    }}
用的时候,传进来 数组名,0, len - 1, 和一个临时数组即可

快排与归并排序的对比

1. 快排和归并排序都应用了分治的思想。快排是交换排序的一种。
2. 快排最坏复杂度为 O(n^2), 与pivot选取有关,会退化为冒泡排序,归并排序最坏也是O(n^2)
3. 归并排序需要另外的O(n)存储空间;快排不需要,直接对原数组操作。
4. 快排每次保证整体有序,即pivot左侧一定都小于pivot,pivot右侧一定都大于pivot,然后分别处理左侧和右侧,使之整体有序,直到只剩一个元素;
归并排序每次保证局部有序,划分到最底部,然后从低开始向上merge合并,从而使整体有序。
5. 归并排序稳定,快排不稳定。

7. 堆排序

堆排序是一种选择排序,关键是下调算法。堆排序的第一步是将近似堆(原数组)转化为最大堆(从最后一个非叶节点开始调用下调算法),然后每次删除根(将根与最后一个节点交换),再使用下调,调整为新的堆,再删除根。。。直到子堆大小为1。
// 下调算法// begin 代表了子堆根,从此开始下调// end 代表了子堆最后一个节点void heap_adjust(int a[], int begin, int end){    for (int i = begin * 2 + 1; i <= end; i = i * 2 + 1)    {        int next = i + 1;        if (next <= end && a[next] > a[i]) i++; // 如果自己的兄弟比自己大,则应该让兄弟和爸爸交换        if (a[begin] > a[i]) break; // 已经满足了堆次序性         swap(a[begin], a[i]);        begin = i; // 以此节点为根继续下调    }}// 先得把无序数组建立为一个大根堆(升序的话)// 使用下调建堆(从最后一个非叶节点开始,重复利用下调),这样就可以// 把建堆和调堆两个过程用一个函数 heap_adjust 进行void heap_sort(int a[], int n){    // 先用建堆算法,建立最大堆,从最后一个非叶节点开始调用下调算法    for (int i = n - 1; i >= 0; i--)        heap_adjust(a, i, n - 1);     // 每次把堆根和最后一个元素交换,然后再用下调算法调堆    for (int i = 0; i < n; i++)    {        swap(a[n - i - 1], a[0]);         heap_adjust(a, 0, n - i - 2);    }}

8. 桶排序

桶排序非常快,但是空间消耗大。
8.1 计数排序 counting sort
基本思路是用一个数组记录每个值出现的次数,数组的下标才是值,数组的值是次数,输出的时候从0开始到max,有几个输出几个。
#include <iostream>#include <algorithm>using namespace std;void countingsort(int a[], int n){    if (n == 0) return;    // 1. 先找最大值    int max = a[0];     for (int i = 0; i < n; i++)        max = (max < a[i] ? a[i]: max);    // 2. 做桶    int * bucket = new int[max + 1];    memset(bucket, 0, sizeof(int)*(max + 1));    for (int i = 0; i < n; i++)        bucket[a[i]] ++;     int k = 0;    for (int i = 0; i < max + 1; i++)    {        int n = bucket[i];         while (n-- > 0)            a[k++] = i;    }    delete[] bucket; }int main(){    int a[] = { 1,7,3,5,2,4,6 };    const int len = sizeof(a) / sizeof(int);    countingsort(a, len);    for (int i = 0; i < len; i++)        cout << a[i] << endl;}

8.2 基数排序是从各位开始装桶。


原创粉丝点击