算法学习—排序总结

来源:互联网 发布:数控铣削与编程实例 编辑:程序博客网 时间:2024/04/29 04:35

排序算法总结,以下为十种排序算法的总结
插入排序、冒泡排序、选择排序、归并排序、快速排序、堆排序、计数排序、基数排序、桶排序、希尔排序;

对排序算法的衡量指标:稳定性、时间复杂度、空间复杂度;

稳定排序(stable sort):插入排序、冒泡排序、归并排序、基数排序、计数排序、桶排序;
不稳定排序(unstable sort):选择排序、快速排序、堆排序,希尔排序;

In-place sort(不占用额外内存或占用常数的内存):插入排序、选择排序、冒泡排序、堆排序、快速排序、希尔排序;
Out-place sort:归并排序、计数排序、基数排序、桶排序;

      • 1 插入排序
      • 2 冒泡排序
      • 3 选择排序
      • 4 归并排序
      • 5 快速排序
      • 6 堆排序
      • 7 计数排序
      • 8 基数排序
      • 9 桶排序
      • 10 希尔排序shell排序


1、 插入排序

基本思想:将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。
要点:设立哨兵,作为临时存储和判断数组边界之用。
如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的,如下图所示:
这里写图片描述

特点: stable sort、In-place sort
最优复杂度:当输入数组就是排好序的时候,复杂度为O(n),而快速排序在这种情况下会产生O(n^2)的复杂度。
最差复杂度:当输入数组为倒序时,复杂度为O(n^2)
插入排序比较适合用于“少量元素的数组”。

实现代码(C++):

void InsertSort(int A[], int N){    for (int i = 1; i < N; i++){        if (A[i] < A[i - 1]){            int j = i - 1;            int mark = A[i];            A[i] = A[i - 1];            while (mark<A[j])            {                A[j + 1] = A[j];                j--;            }            A[j + 1] = mark;        }    }}

2、 冒泡排序

自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒;
这里写图片描述

**特点:**stable sort、In-place sort
最坏运行时间:O(n^2)
最佳运行时间:O(n^2)(当然,也可以进行改进使得最佳运行时间为O(n));

代码实现:

void BubbleSort(int A[], int N){    for (int i = 0; i < N-1; i++){        for (int j = 0; j < N - i - 1; j++){            if (A[j] > A[j + 1]){                int tmp = A[j];                A[j] = A[j + 1];                A[j + 1] = tmp;            }        }    }}

冒泡排序的改进:
1. 设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。

void BubbleSort_1(int A[], int N) {    int i = N - 1;  //初始时,最后位置保持不变      while (i> 0) {        int pos = 0; //每趟开始时,无记录交换          for (int j = 0; j< i; j++)            if (A[j]> A[j + 1]) {                pos = j; //记录交换的位置                   int tmp = A[j];                 A[j] = A[j + 1];                 A[j + 1] = tmp;            }        i = pos; //为下一趟排序作准备      }}
  1. 利用在每趟排序中进行正向和反向两遍冒泡的方法一次可以得到两个最终值(最大者和最小者) , 从而使排序趟数几乎减少了一半。
void BubbleSort_2(int A[], int N){    int low = 0;    int high = N - 1; //设置变量的初始值      int tmp, j;    while (low < high) {        for (j = low; j< high; ++j) //正向冒泡,找到最大者              if (A[j]> A[j + 1]) {                tmp = A[j];                 A[j] = A[j + 1];                 A[j + 1] = tmp;            }        --high;                 //修改high值, 前移一位          for (j = high; j>low; --j) //反向冒泡,找到最小者              if (A[j]<A[j - 1]) {                tmp = A[j];                A[j] = A[j - 1];                A[j - 1] = tmp;            }        ++low;                  //修改low值,后移一位      }}

3、 选择排序

在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
这里写图片描述
**特性:**In-place sort,unstable sort。
最好情况时间:O(n^2)。
最坏情况时间:O(n^2)。

实现代码:

void SeleteSort(int A[], int N){        for (int i = 0; i < N; i++){        int minnum = A[i];        int minnum_index = i;        for (int j = i+1; j < N; j++){            if (A[j] < minnum){                minnum = A[j];                minnum_index = j;            }        }        if (minnum_index != i){            int tmp = A[i];            A[i] = A[minnum_index];            A[minnum_index] = tmp;        }    }}

4、 归并排序

归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
这里写图片描述

**特点:**stable sort、Out-place sort
最坏情况运行时间:O(nlgn)
最佳运行时间:O(nlgn)

实现代码:

//归并排序 void Merge(int *r, int *rf, int i, int m, int n){    int j, k;    for (j = m + 1, k = i; i <= m && j <= n; ++k){        if (r[j] < r[i]) rf[k] = r[j++];        else rf[k] = r[i++];    }    while (i <= m)  rf[k++] = r[i++];    while (j <= n)  rf[k++] = r[j++];    //print(rf, n + 1);}int * MergeSort(int *r,  int lenght){    int *rf = new int[lenght];    int len = 1;    int *q = r;    int *tmp;    while (len <= lenght) {        int s = len;        len = 2 * s;        int i = 0;        while (i + len <lenght){            Merge(q, rf, i, i + s - 1, i + len - 1); //对等长的两个子表合并              i = i + len;        }        if (i + s < lenght){            Merge(q, rf, i, i + s - 1, lenght - 1); //对不等长的两个子表合并          }        tmp = q;         q = rf;         rf = tmp; //交换q,rf,以保证下一趟归并时,仍从q 归并到rf      }    return rf;}

两路归并的递归算法

void MSort(ElemType *r, ElemType *rf,int s, int t)  {       ElemType *rf2;      if(s==t) r[s] = rf[s];      else      {           int m=(s+t)/2;          /*平分*p 表*/          MSort(r, rf2, s, m);        /*递归地将p[s…m]归并为有序的p2[s…m]*/          MSort(r, rf2, m+1, t);      /*递归地将p[m+1…t]归并为有序的p2[m+1…t]*/          Merge(rf2, rf, s, m+1,t);   /*将p2[s…m]和p2[m+1…t]归并到p1[s…t]*/      }  }  void MergeSort_recursive(ElemType *r, ElemType *rf, int n)  {   /*对顺序表*p 作归并排序*/      MSort(r, rf,0, n-1);  } 

5、 快速排序

基本思想:
1)选择一个基准元素,通常选择第一个元素或者最后一个元素,
2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。
3)此时基准元素在其排好序后的正确位置
4)然后分别对这两部分记录用同样的方法继续进行排序,直到整
个序列有序。

快速排序的一次过程:
这里写图片描述

快速排序的全过程:
这里写图片描述

**特性:**unstable sort、In-place sort。
最坏运行时间:当输入数组已排序时,时间为O(n^2),当然可以通过随机化来改进(shuffle array 或者 randomized select pivot),使得期望运行时间为O(nlgn)。
最佳运行时间:O(nlgn)


6、 堆排序

堆:分为大根堆、小跟堆;
大根堆:根节点>叶子节点
小跟堆:根节点<叶子节点
因此堆的根节点(堆顶元素)是最大或者最小的,一个堆对应于一个二叉树。

堆排序的思想:
初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列;

对无序序列进行创建小跟堆:(49,38,65,97,76,13,27,49)

这里写图片描述

**特性:**unstable sort、In-place sort。
最优时间:O(nlgn)
最差时间:O(nlgn)

实现代码:

//堆排序算法void HeapAdjust(int H[], int s, int length){    int tmp = H[s];    int child = 2 * s + 1;    while (child<length)    {        if (child + 1 < length&&H[child] < H[child + 1]){            ++child;        }        if (H[s] < H[child]){            H[s] = H[child];            s = child;            child = 2 * s + 1;        }        else        {            break;        }        H[s] = tmp;    }}void BuildingHeap(int H[], int length){    for (int i = (length - 1) / 2; i >= 0; --i){        HeapAdjust(H, i, length);    }}void HeapSort(int H[], int length){    BuildingHeap(H, length);    for (int i = length - 1; i > 0; i--){        int temp = H[i];        H[i] = H[0];        H[0] = temp;        HeapAdjust(H, 0, i);    }}

7、 计数排序

输入一个数X,确定小于X的元素的个数,这样,就可以把这个数放在输出数组的指定位置上。
  假设输入数组是A[1…n],则需要一个辅助数组C[0…K],一个输出数组B[1…n]。其中k代表输入数组中的最大值,n代表输入数组的长度。
  其中,输入数组A是需要进行排序的一组数据,输出数组B是需要排序完成后的数据。辅助数组中是按键值存储该键值在输入数组中出现的次数。

  1. 初始化辅助数组。
  2. 循环遍历每一个输入元素,如果一个输入元素为i,则辅助数组中相应的C[i]的值加1。执行完毕之后。数组C中存储的就是各个键值在输入数组中出现的次数。
  3. 再通过加总计算确定对于从1到k,有多少个输入元素是小于等于k的。将结果赋值到数组C中。
  4. 循环将A[J]放到它在输出数组的正确位置上。对于一个值来说,C[A[J]]的值就是它在输出数组B中的正确位置。

**特性:**stable sort、out-place sort。
最坏情况运行时间:O(n+k)
最好情况运行时间:O(n+k)

当k=O(n)时,计数排序时间为O(n)

//计数排序n:数组长度,k:数组中最大的值int * CountSort(int A[], int n,int k){    int *B = new int[k];    int *result = new int[n];    for (int i = 0; i < k; i++){        B[i] = 0;    }    for (int i = 0; i < n; i++){        ++B[A[i]];    }    for (int i = 1; i < k; i++){        B[i] = B[i] + B[i - 1];    }    for (int i = (n-1); i >= 0; i--){        int elem = A[i];        int index = B[elem] - 1;        result[index] = elem;        --B[elem];    }    return result;}

8、 基数排序

是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。

这里写图片描述

**特性:**stable sort、Out-place sort。
最坏情况运行时间:O((n+k)d)
最好情况运行时间:O((n+k)d)
d 为位数,r 为基数,n 为原数组个数

//基数排序/**求数据的最大位数,决定排序次数*/int maxbit(int data[], int n){    int d = 1; //保存最大的位数      int p = 10;    for (int i = 0; i < n; ++i)    {        while (data[i] >= p)        {            p *= 10;            ++d;        }    }    return d;}void RadixSort(int data[], int n){    int d = maxbit(data, n);    int *tmp = new int[n];    int count[10]; //计数器      int i, j, k;    int radix = 1;    for (i = 1; i <= d; i++) //进行d次排序      {        for (j = 0; j < 10; j++)            count[j] = 0; //每次分配前清空计数器          for (j = 0; j < n; j++)        {            k = (data[j] / radix) % 10; //统计每个桶中的记录数              count[k]++;        }        for (j = 1; j < 10; j++)            count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶          for (j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中          {            k = (data[j] / radix) % 10;            tmp[count[k] - 1] = data[j];            count[k]--;        }        for (j = 0; j < n; j++) //将临时数组的内容复制到data中              data[j] = tmp[j];        radix = radix * 10;    }}

9、 桶排序

是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。
例如要对大小为[1..1000]范围内的n个整数A[1..n]排序

  1. 可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储 (10..20]的整数,……集合B[i]存储( (i-1)*10, i*10]的整数,i = 1,2,..100。总共有 100个桶。
  2. 对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]中。 再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任 何排序法都可以。
  3. 依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这 样就得到所有数字排好序的一个序列了。

**特性:**out-place sort、stable sort。
最坏情况运行时间:当分布不均匀时,全部元素都分到一个桶中,则O(n^2),当然[算法导论8.4-2]也可以将插入排序换成堆排序、快速排序等,这样最坏情况就是O(nlgn)。
最好情况运行时间:O(n)

//桶排序const int BUCKET_NUM = 10;struct ListNode{    explicit ListNode(int i = 0) :mData(i), mNext(NULL){}    ListNode* mNext;    int mData;};ListNode* insert(ListNode* head, int val){    ListNode dummyNode;    ListNode *newNode = new ListNode(val);    ListNode *pre, *curr;    dummyNode.mNext = head;    pre = &dummyNode;    curr = head;    while (NULL != curr && curr->mData <= val){        pre = curr;        curr = curr->mNext;    }    newNode->mNext = curr;    pre->mNext = newNode;    return dummyNode.mNext;}ListNode* Merge(ListNode *head1, ListNode *head2){    ListNode dummyNode;    ListNode *dummy = &dummyNode;    while (NULL != head1 && NULL != head2){        if (head1->mData <= head2->mData){            dummy->mNext = head1;            head1 = head1->mNext;        }        else{            dummy->mNext = head2;            head2 = head2->mNext;        }        dummy = dummy->mNext;    }    if (NULL != head1) dummy->mNext = head1;    if (NULL != head2) dummy->mNext = head2;    return dummyNode.mNext;}void BucketSort(int arr[], int n){    vector<ListNode*> buckets(BUCKET_NUM, (ListNode*)(0));    for (int i = 0; i < n; ++i){        int index = arr[i] / BUCKET_NUM;        ListNode *head = buckets.at(index);        buckets.at(index) = insert(head, arr[i]);    }    ListNode *head = buckets.at(0);    for (int i = 1; i < BUCKET_NUM; ++i){        head = Merge(head, buckets.at(i));    }    for (int i = 0; i < n; ++i){        arr[i] = head->mData;        head = head->mNext;    }}

10、 希尔排序(shell排序)

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

操作方法:
1. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
2. 按增量序列个数k,对序列进行k 趟排序;
3. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

这里写图片描述

**特性:**out-place sort、unstable sort。
平均运行时间:O(n^1.3)
最坏情况运行时间:O(n^2)
最好情况运行时间:O(n)

//希尔排序/*** 直接插入排序的一般形式*dk 缩小增量,如果是直接插入排序,dk=1*/void ShellInsertSort(int a[], int n, int dk){    for (int i = dk; i<n; ++i){        if (a[i] < a[i - dk]){          //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入              int j = i - dk;            int x = a[i];           //复制为哨兵,即存储待排序元素              a[i] = a[i - dk];         //首先后移一个元素              while (x < a[j]){     //查找在有序表的插入位置                  a[j + dk] = a[j];                j -= dk;             //元素后移              }            a[j + dk] = x;            //插入到正确位置          }    }}/*** 先按增量d(n/2,n为要排序数的个数进行希尔排序*/void shellSort(int a[], int n){    int dk = n / 2;    while (dk >= 1){        ShellInsertSort(a, n, dk);        dk = dk / 2;    }}

这里写图片描述

原创粉丝点击