排序算法总结

来源:互联网 发布:单片机系统时钟频率 编辑:程序博客网 时间:2024/06/04 19:00

一、插入排序

  • 直接插入排序
    • 每次选择一个元素K插入到之前已排好序的部分A[1i]中,插入过程中K依次由后向前与A[1i]中的元素进行比较。若发现发现A[x]K,则将K插入到A[x]的后面,插入前需要移动元素。
      这里写图片描述
    • 时间复杂度:
      • 最好的情况下:正序有序(从小到大),这样只需要比较n次,不需要移动。因此时间复杂度为O(n)
      • 最坏的情况下:逆序有序,这样每一个元素就需要比较n次,共有n个元素,因此实际复杂度为O(n­2)
    • 稳定性:插入排序是稳定的。(K1是已排序部分中的元素,当K2和K1比较时,直接插到K1的后面(没有必要插到K1的前面,这样做还需要移动)
void Dir_Insert(int A[],int N){    int j,t;    for(int i=1;i<N;i++){        t=A[i];        j=i-1;        while(A[j]>t){            A[j+1]=A[j];            j--;        }        A[j+1]=t;    }}
  • 希尔排序
    • 分组插入方法。先取定一个小于n的整数d1作为第一个增量,把表的全部记录分成d1个组,所有距离为d1的倍数的记录放在同一个组中,在各组内进行直接插入排序;然后,取第二个增量d2(<d1),重复上述的分组和排序,直至所取的增量dt=1
    • 时间复杂度:
      • 最好情况:未知。
      • 最坏情况:O(N*logN),最坏的情况下和平均情况下差不多。
    • 稳定性:不稳定。(不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱)
void Shell(int A[],int n) {    int i,j,k,t;    (n/2)%2 == 0 ? k = n/2+1 : k = n/2; //保证增量为奇数    while(k > 0){        for(j=k;j<n; j++){            t = A[j];            i = j - k;            while(i>=0 && A[i]>t){                A[i+k]=A[i];                i=i-k;            }            A[i+k]=t;        }        if(k == 1) break;        (k/2)%2 ==0 ? k=k/2+1 : k=k/2;    }}

二、交换排序

  • 冒泡排序:临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换, 这样一趟过去后,最大或最小的数字被交换到了最后一位, 然后再从头开始进行两两比较交换,直到倒数第二位时结束
void BubbleSort_1(int a[], int size){    for (int i = 0; i < size -1; i++)    {        for (int j = size - 1; j > i ; j--)        {            if (a[j-1] > a[j])            {                int temp = a[j-1];                a[j-1] = a[j];                a[j] = temp;            }        }    }}
    - 优化1: 如果上面代码中,里面一层循环在某次扫描中没有执行交换,则说明此时数组已经全部有序列,无需再扫描了。因此,增加一个标记,每次发生交换,就标记,如果某次循环完没有标记,则说明已经完成排序。
void BubbleSort_2(int a[], int size){    bool bSwaped = true;    for (int i = 0; i < size -1; i++)    {        // 每次先重置为false        bSwaped = false;        for (int j = size - 1; j > i ; j--)        {            if (a[j-1] > a[j])            {                int temp = a[j-1];                a[j-1] = a[j];                a[j] = temp;                bSwaped = true;            }        }        // 如果上一次扫描没有发生交换,则说明数组已经全部有序,退出循环        if (!bSwaped)            break;    }}
    - 优化2:如果R[0..i]已是有序区间,上次的扫描区间是R[i..n],记上次扫描时最后 一次执行交换的位置为lastSwapPos,则lastSwapPos在i与n之间,不难发现R[i..lastSwapPos]区间也是有序的,否则这个区间也会发生交换;所以下次扫描区间就可以由R[i..n] 缩减到[lastSwapPos..n]
void BubbleSort_3(int a[], int size){    int lastSwapPos = 0,lastSwapPosTemp = 0;    for (int i = 0; i < size - 1; i++)    {        lastSwapPos = lastSwapPosTemp;        for (int j = size - 1; j >lastSwapPos; j--)        {            if (a[j - 1] > a[j])            {                int temp = a[j - 1];                a[j - 1] = a[j];                a[j] = temp;                lastSwapPosTemp = j;            }        }        if (lastSwapPos == lastSwapPosTemp)            break;    }}
  • 快速排序:采用的思想是分治思想。
void quicksort(int left,int right) {     int i,j,t,temp;     if(left>right)        return;     temp=a[left]; //temp中存的就是基准数     i=left;     j=right;     while(i!=j)     {                    //顺序很重要,要先从右边开始找                    while(a[j]>=temp && i<j)                             j--;                    //再找右边的                    while(a[i]<=temp && i<j)                             i++;                    //交换两个数在数组中的位置                    if(i<j)                    {                             t=a[i];                             a[i]=a[j];                             a[j]=t;                    }     }     //最终将基准数归位     a[left]=a[i];     a[i]=temp;     quicksort(left,i-1);//继续处理左边的,这里是一个递归的过程     quicksort(i+1,right);//继续处理右边的 ,这里是一个递归的过程 }

三、选择排序

  • 直接选择排序:从所有序列中先找到最小的,然后放到第一个位置。之后再看剩余元素中最小的,放到第二个位置……以此类推,就可以完成整个的排序工作了。
for(int i=0; i<v.size(); i++){                int min = v[i];                 int temp;                int index = i;                for(int j=i+1;j<v.size();j++){                    if(v[j] < min){                         min = v[j];                         index = j;                    }                       }                       temp = v[i];                 v[i] = min;                v[index]= temp;        }
  • 堆排序
    • 堆存储:数组来表示堆,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。
    • 建堆
    • 初始状态:
      这里写图片描述
    • 调整:
      这里写图片描述
    • 排序
    • 整体步骤
      • 首先从第一个非叶子节点开始,比较当前节点和其孩子节点,将最大的元素放在当前节点,交换当前节点和最大节点元素。
      • 将当前元素前面所有的元素都进行1的过程,这样就生成了最大堆
      • 将堆顶元素和最后一个元素交换,列表长度减1。由此无序区减1,有序区加1
      • 剩余元素重新调整建堆
      • 继续3和4,直到所有元素都完成排序
int adjust_heap(vector<int> &v, int length, int i){        int left = 2 * i;        int right = 2 * i + 1;        int largest = i;        int temp;        while(left < length || right < length){                if (left < length && v[largest] < v[left]){                        largest = left;                }                if (right < length && v[largest] < v[right]){                        largest = right;                }                if (i != largest){                        temp = v[largest];                        v[largest] = v[i];                        v[i] = temp;                        i = largest;                        left = 2 * i;                        right = 2 * i + 1;                }                else{                        break;                }        }}int build_heap(vector<int> &v, int length){        int i;        int begin = length/2 - 1; //get the last parent node        for (i = begin; i>=0; i--){                adjust_heap(v,length,i);        }}int heap_sort(vector<int> &v){        int length = v.size();        int temp;        printline("before sort:",v);        build_heap(v,length);        while(length > 1){                temp = v[length-1];                v[length-1] = v[0];                v[0] = temp;                length--;                adjust_heap(v,length,0);        }        printline("after sort:",v);}
- 时间复杂度:平均时间复杂度为O(nlogn),接近于最坏的时间复杂度。在最好情况下,时间复杂度为O(1)。

四、归并排序

  • 分治思想
//将有二个有序数列a[first...mid]和a[mid...last]合并。void mergearray(int a[], int first, int mid, int last, int temp[]){    int i = first, j = mid + 1;    int m = mid,   n = last;    int k = 0;    while (i <= m && j <= n)    {        if (a[i] <= a[j])            temp[k++] = a[i++];        else            temp[k++] = a[j++];    }    while (i <= m)        temp[k++] = a[i++];    while (j <= n)        temp[k++] = a[j++];    for (i = 0; i < k; i++)        a[first + i] = temp[i];}void mergesort(int a[], int first, int last, int temp[]){    if (first < last)    {        int mid = (first + last) / 2;        mergesort(a, first, mid, temp);    //左边有序        mergesort(a, mid + 1, last, temp); //右边有序        mergearray(a, first, mid, last, temp); //再将二个有序数列合并    }}bool MergeSort(int a[], int n){    int *p = new int[n];    if (p == NULL)        return false;    mergesort(a, 0, n - 1, p);    delete[] p;    return true;}
  • 时间复杂度分析和改进方法:参考 浅谈算法和数据结构: 三 合并排序

五、计数排序

  • 基本思想:计数排序假设n个输入元素中的每一个都是介于0-k的整数,此处k为某个整数。计数排序顾名思义离不开计数,我们要计的是输入元素中相同元素出现的次数。对每一个输入元素x,确定小于x的元素的个数,那样排序之后,x在最终输出数组中的位置就可以确定了。
  • 步骤:
    • 找出待排序的数组中最大和最小的元素
    • 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
    • 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
    • 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
#include <stdio.h>#include <stdlib.h>#include <time.h>/* run this program using the console pauser or add your own getch, system("pause") or input loop */void print_arry(int *arr,int n){    int i;    for(i = 0; i<n; i++)    {        printf("%d ", arr[i]);    }    printf("\n");}void count_sort(int *arr, int *sorted_arr, int n){    int *count_arr = (int *)malloc(sizeof(int) * 100);    int i;     //初始化计数数组     for(i = 0; i<100; i++)        count_arr[i] = 0;    //统计i的次数     for(i = 0;i<n;i++)        count_arr[arr[i]]++;    //对所有的计数累加     for(i = 1; i<100; i++)        count_arr[i] += count_arr[i-1];     //逆向遍历源数组(保证稳定性),根据计数数组中对应的值填充到先的数组中     for(i = n; i>0; i--)    {        sorted_arr[count_arr[arr[i-1]]-1] = arr[i-1];        count_arr[arr[i-1]]--;      }     free(count_arr);}int main() {    int n,i;    printf ("待排序数组的大小 n=");     scanf ("%d", &n);     int *arr = (int *)malloc(sizeof(int) * n);    int *sorted_arr = (int *)malloc(sizeof(int) * n);    srand (time (0));    for (i = 0; i<n; i++)    {        arr[i] = rand() % 100;    }    printf ("随机生成数值为0~99的数组...\n");    printf ("初始化数组: ");    print_arry(arr, n);    count_sort(arr, sorted_arr, n);    printf ("排序后的数组:");     print_arry(sorted_arr, n);    return 0;    system ("pause");}
  • 特点:
    • 提前必须是已知待排序的关键字为整型且范围已知。
    • 时间复杂度为O(n+k),不是基于比较的排序算法,因此效率非常之高。
    • 稳定性好,这个是计数排序非常重要的特性,可以用在后面介绍的基数排序中。
    • 但需要一些辅助数组,如C[0..k],因此待排序的关键字范围0~k不宜过大。而B[1..n]用来存放排序结果,我们可以对上述算法进行改进,使排序在原地进行。改进之后如下:
memset(C,sizeof(C),0);       //C数组置零  for i=1 to n do          C[A[i]]++;               //统计输入数组中相同元素的个数  idx = 0;  for i=0 to k do                       while(C[i]>0) do         //C[i]中保存的是值为i元素的个数              A[idx++] = i;        //因此很容易找到i在A中适合的位置              C[i]--; 

六、桶排序

  • 参考: 经典排序算法 - 桶排序Bucket sort
0 0