经典排序算法

预定义

#define Ty int // 以整型为例

交换数据的函数

void swap(Ty* elems,int i,int j) {    Ty temp=elems[i];    elems[i]=elems[j];    elems[j]=temp;}// 如果确认只用整型数据使用下面的代码会更快,因为减少了函数压栈,也使用了位运算#define SWAP_INT(a, b) \    {                  \        a = (a) ^ (b); \        b = (a) ^ (b); \        a = (a) ^ (b); \    }

冒泡排序

原理:将一段序列的最大值(最小值)拿到最左边或者最右边的操作,使用循环重复操作,(每轮排序都会少一个最大值或最小值),当最后只剩下一个数据的时候整个序列就已经排好序了。
冒泡排序的原理很简单,使用代码实现也很简单。也是所有排序算法里面最简单的一个。
具体代码实现:

void bubSort(Ty* elems,int size) {    for (int i = 0;i < size;size--) {    //轮        for (int j = i + 1;j < size;j++) {    //找剩余元素中的最值            if (elems[j] < elems[j-1]) {                swap(elems,j,j-1);            }        }    }}void bubSort(Ty* elems,int size) {    for(int i=0;i<size-1;i++)        for(int j=i+1;j<size;j++)            if(elems[i]<elems[j])                swap(elems,i,j);}

直接使用两层循环去实现,外层循环主要作用是存放最大值或最小值的,内存循环的主要作用是找到发生冲突的元素,如果发生冲突就交换两个数据。当两层循环的结束的时候整个序列就自然排好序了。时间复杂度为O(n^2).

选择排序

原理:基本思想和冒泡排序是一样的,选择排序相对于冒泡排序的优点就是减少交换次数。算法思想都是在序列中找到最大值(最小值),然后存放好下次进入循环就访问不到这个最大值(最小值)。当两层循环都结束的时候序列就自然排好了。
具体代码实现:

void selectSort(Ty* elems,int size) {    for(int i=0;i<size-1;i++) {        int minPos=i;        for(int j=i+1;jelems[j])                minpos=j;        swap(elems,i,minPos);    }}

插入排序

原理:基本思想还是冒泡排序,不过插入排序是两边相靠的冒泡,所以在序列部分有序的情况下,插入排序的效率要比冒泡排序效率高。从序列的尾部开始往前比较,如果当前的数据小于(大于)前一个的数据就进行交换,否则进入下一次循环,直到外层循环遍历完整个序列就自然排好序了。
具体代码实现:

//使用交换函数void insertSort(Ty* elems,int size) {    for(int i = 1;i  0 && elems[j-1] > elems[j];j--)            swap(elems,j-1,j);}//不使用交换函数void insertSort(Ty* elems,int size) {    for (int i = 1;i = 1 && temp < arr[j-1];j--)            arr[j] = arr[j-1];        arr[j] = temp;    }}

希尔排序

原理:希尔排序是建立在插入排序的基础上进行优化的排序算法,所以希尔排序又叫做优化版的插入排序。
代码实现:

  1. 将间隔定为4
void shellSort(Ty* elems,int size) {    for(int h = 4;h >= 1;h = h >> 1)        for(int i = h;i = h && elems[j] > elems[j-h];j -= h)                swap(elems,j,j-h);}
  1. 使用常用的序列号
//使用交换函数void shellSort(Ty* elems,int size) {    int h = 1;    while(h = 1) {        for(int i = h;i = h && elems[j] > elems[j-h];j -= h)                swap(elems,j,j-h);        h /= 3;    }}//不使用交换函数void shellSort(Ty* elems,int size) {    int h = 1;    int t = length/3;    while (h = 1) {        for (int i = h;i = h && temp < arr[j-h];j-=h)                arr[j] = arr[j-h];            arr[j] = temp;        }        h /= 3;    }}
# 实例解释size=13,h=4 # h=13/3        h        |4 6 8 7 9 5 3 1 2 10 0 11 12        ||        ij# 45 交换 j-=h        h        |4 5 8 7 9 6 3 1 2 10 0 11 12  |          |  j          i# j<h i++;j=i# 依此类推

快速排序

原理:快速排序的核心思想是设立一个轴,然后其他数据都和这个轴作比较,最后把轴放在序列的中间,执行完一遍快速排序后左边的数据都比轴小,右边的数据都比轴大。然后递归下去,当递归结束的时候就拍好序了。快速排序的排序很快,但是当数据形成一边倒的情况的时候就发挥不出快速排序的优势。
具体代码实现:

//稍微修改一下适用于单链表的快速排序void quickSort(Ty *elems, int first, int last) {    if (first >= last || first < 0 || last < 0)        return;    Ty privot = elems[first];    int i = first + 1;    int j = first + 1;    while (j <= last) {        if (elems[j] = right) return;    int i = left;    int j = right;    Ty privot = elems[i];    while (i < j) {        while (i = privot) --j;        arr[i] = arr[j];        while (i < j && arr[i] <= privot) ++i;        arr[j] = arr[i];    }    arr[i] = privot;    quickSort(elems,left,i - 1);    quickSort(elems,i + 1,right);}

归并排序

原理:把要排序的序列拆分成多个含有一个数据的序列,然后按照从小到大(从大到小)进行合并,这样就自然的将无序的序列排好序。
具体代码实现:

void merge(int arr[],int left,int mid,int right,int* temp) {    int i = left;    int j = mid + 1;    int k = left;    while (i <= mid && j <= right) temp[k++] = arr[i] < arr[j] ? arr[i++] : arr[j++];    while (i <= mid) temp[k++] = arr[i++];    while (j <= right) temp[k++] = arr[j++];    for (i = left;i = right) return;    int mid = left + ((right - left) >> 1);    merge_sort(arr,left,mid,temp);    merge_sort(arr,mid + 1,right,temp);    merge(arr,left,mid,right,temp);}void mergeSort(int arr[],int length) {    int* temp = (int*)malloc(sizeof(int)*length);    assert(temp);    merge_sort(arr,0,length - 1,temp);    free(temp);}

堆排序

堆分有小顶堆和大顶堆,而堆排序又分有外堆和内堆。

  • 外堆
    意思是,而外申请一段和原来数组一样大的内存大小,并将数组的元素构造成小顶堆或大顶堆。根据排序的顺序和逆序确定要构成的堆结构是大顶堆还是小顶堆。现以最终序列是顺序排序(从小到大)为例,则需要构成的堆结构是小顶堆。因为小顶堆可以快速的找到序列的最小值,如果将小顶堆的数据依次弹出,则每次弹的都是剩余序列的最小值,并且每次弹出就放到原来的数组中,当小顶堆里的数据都弹出来完了,原来的数组也自然有序了。如果要求最终序列是逆序的则构造的堆结构是大顶堆,然后操作是一样的。时间复杂度是\(O(nlogn)\). 空间复杂度是\(O(n)\).

  • 内堆
    意思是,不需要额外申请空间,直接在原来的数组上进行操作。现以最终序列是顺序排序(从小到大)为例,则需要构成的堆结构是大顶堆。在原来的数组上构造堆结构称之为 “堆化” ,heapify.形成大顶堆后,将堆顶依次弹出并立即放数组的尾部,反复操作此步骤直到最后一个数据,最终自然形成从小到大依次排好序。

//外堆//堆的结构体描述typedef struct Heap {    Ty* root;    int size;}Heap;//创建堆内存Heap* creatHeap(int capacity) {    Heap* heap = (Heap*)malloc(sizeof(Heap));    assert(heap);    heap->root = (Ty*)malloc(sizeof(Ty) * (capacity+1) );    heap->size = 1;    return heap;}//入堆//先将要插入的数据插入到堆的尾部,然后向上渗透,爬到对应的位置,就把数据放进去即可void pushHeap(Heap* heap,Ty data) {    int current = heap->size++;    int parent  = current >> 1;     heap->root[current] = data;    while (parent) {        if (heap->root[current] root[parent]) {            swap(heap->root,current,parent);                current = parent;            parent >>= 1;            }        else break;    }}//出堆//先将堆顶元素保存下来,然后使用堆的尾部覆盖堆顶,然后往下渗透,走到对应的位置,就把数据放进去,然后返回保存的元素Ty popHeap(Heap* heap) {    int current = 1;    int rchild  = 3;    int n = --heap->size;    Ty ret = heap->root[1];    heap->root[1] = heap->root[n];    while (rchild root[rchild - 1] root[rchild] ? rchild - 1 : rchild;        if (heap->root[small] root[current]) {            swap(heap->root,current,small);            current = small;            rchild  = (current << 1) + 1;        }        else break;    }    return ret;}
//内堆//在原来数组的基础上直接操作,其实就是入堆和出队直接结合,不需要额外申请空间void heapify(int arr[],int current,int length) {    int rchild = (current << 1) + 2;    int large;    while (rchild  arr[rchild] ? rchild-1 : rchild)] > arr[current])) {        swap(arr,large,current);        current = large;        rchild  = (current <> 1;    while (current >= 0) heapify(arr,current--,length);    while (length) {        swap(arr,0,--length);        heapify(arr,0,length);    }}

计数排序

前面的算法都是基于比较的排序,计数排序是利用了数组的下标天然有序原理进行排序,所以计数排序是基于统计而排序的排序算法。算法的核心思想是遍历一个无序数组,将遍历到的数据按它的数值找到统计数组的对应下标将其放入统计数组中,依次类推,直到无序的数组的每一个数据都被遍历完之后统计数组也已经初始化完毕,接下来就是遍历统计数组如果遍历到的空间是大于零的就将其下标存入原来的数组中,直到统计数组被遍历完,最终可以排好序。

void countSort(int arr[],int length,int max) {    int* count = (int*)calloc(max,sizeof(int));    for (int i = 0;i < length;i++) {        count[arr[i]]++;    }    for (int i = 0,j = 0;i < max;i++) {        while (count[i]--)            arr[j++] = i;    }    free(count);}

基数排序

桶排序的思想,按照数字的位数进行排序,准备0-9的链式队列,从低位开始到高位进行排序,当最高位被排好序后原来的序列自然排好序了。
例如:对以下序列进行基数排序
578,234,86,432,512,618,384
排序过程:
第一轮(\(在第零轮的基础上按10^0位排\)):432,512,234,384,86,578,618
第二轮(\(在第一轮的基础上按10^1位排\)):512,618,432,234,578,384,86
第三轮(\(在第二轮的基础上按10^2位排\)):86,234,384,432,512,578,618
第三轮结束序列自然排好序。86不够3位数,就往前面补零,即86 = 086.

void redixSort(int arr[], int length) {    int i;    int j;    int ii;    int jj;    int temp[10][10];    for (ii = 0; ii < 10; ii++)        for (jj = 0; jj < 10; jj++)            temp[ii][jj] = -1;    for (int k = 10; k < 10000; k *= 10) {        for (i = 0; i < length; i++) {            int index = (arr[i] % k) / (k / 10);            j = 0;            while (temp[index][j] != -1)                j++;            temp[index][j] = arr[i];        }        i = 0;        for (ii = 0; ii < 10; ii++) {            for (jj = 0; jj < length && temp[ii][jj] != -1; jj++) {                arr[i++] = temp[ii][jj];                temp[ii][jj] = -1;            }        }    }}

我在去年已经先将视频讲解发布到B站上了,但是当时没有将文档发布,直接放到博客里让大家访问和学习吧。下面是本次内容的视频链接:
B站视频链接:Cukor丘克