前言说明
十大排序算法可以说是每个程序员最早接触的也是最重要的算法,这一块必须要非常熟练的掌握,应该要理解每种排序的思想,对各个排序的性能的了解也是基础且重要的。为了方便自己和大家学习,决定整合成一篇文章,每种算法会有通俗易懂的算法思想描述,还配有动画、代码说明和算法改进。

本文参考了百度百科,CSDN中的一些文章加上我自己的一些理解。水平一般,能力有限,欢迎大家一起讨论。*

一、排序算法

1. 排序的概念

所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

2.术语说明

稳定 :如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定 :如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
内排序:在对待排序数据存放在内存中进行的排序过程。是我们主要讨论与学习的重点。
外排序:待排数据量太大,无法一次性将所有待排序数据放入内存中,在排序过程中需要对磁盘等外部储存器进行访问。不是我们谈论与学习的重点,但也要通过相关资料或书籍了解其基本原理。
时间复杂度 : 一个算法执行所耗费的时间。
空间复杂度 :运行完一个程序所需内存的大小。

比较排序:排序过程中需要对数据关键字进行比较。
非比较排序:排序过程中不需要对数据关键字进行比较。
常见的快速排序、归并排序、堆排序、冒泡排序 等属于比较排序 。在排序的最终结果里,元素之间的次序依赖于它们之间的比较。每个数都必须和其他数进行比较,才能确定自己的位置 。
在冒泡排序之类的排序中,问题规模为n,又因为需要比较n次,所以平均时间复杂度为O(n²)。在归并排序、快速排序之类的排序中,问题规模通过分治法消减为logN次,所以时间复杂度平均O(nlogn)。
比较排序的优势是,适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。
计数排序、基数排序、桶排序则属于非比较排序 。非比较排序是通过确定每个元素之前,应该有多少个元素来排序。针对数组arr,计算arr[i]之前有多少个元素,则唯一确定了arr[i]在排序后数组中的位置 。
非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。算法时间复杂度O(n)。
非比较排序时间复杂度底,但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。

3.分类


除了图上的九种算法,还有一种桶排序未在上图中。

4.各算法时间、空间复杂度

所有算法的性质全在表中列出,介绍算法时不再赘述!

图片名词解释:
时间复杂度和空间复杂度的解释请参考:
https://blog.csdn.net/zxm490484080/article/details/72210501
n: 数据规模
k: “桶”的个数
In-place: 占用常数内存,不占用额外内存
Out-place: 占用额外内存
稳定性:在上文已经讨论过了

二、十种排序算法

1.直接插入排序

1.1原理

将数组的第一个数认为是有序数组,从后往前(从前往后)扫描该有序数组,把数组中其余n-1个数,根据数值的大小,插入到有序数组中,直至数组中的所有数有序排列为止。这样的话,n个元素需要进行n-1趟排序。

1.2算法描述

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
步骤1: 从第一个元素开始,该元素可以认为已经被排序;
步骤2: 取出下一个元素,在已经排序的元素序列中从后向前扫描;
步骤3: 如果该元素(已排序)大于新元素,将该元素移到下一位置;
步骤4: 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
步骤5: 将新元素插入到该位置后;
步骤6: 重复步骤2~5。

1.3动图演示

1.4相关代码

#include<iostream>
#include<cstdio>
using namespace std;
#define N 5
int a[N];//有序数组
int main ( )
{int i, k, x;printf("Please input %d numbers:\n",N);   for (i=0; i<N; i++) {scanf ("%d", &x);for ( k=i; k>0; k-- ) {          /* 从后向前比较 */if ( a[k-1] > x )    //x前面的数比它大a[k]=a[k-1];         /* 将大数向后移动*/else      break; /* 找到插入的位置,退出 */}a[k] = x;  /* 完成插入操作 */}  for (i=0; i<N; i++)printf("%d ", a[i]);return 0;
}

1.5算法改进

在插入某个元素之前需要先确定该元素在有序数组中的位置,上例的做法是对有序数组中的元素逐个扫描,当数据量比较大的时候,这是一个很耗时间的过程,可以采用二分查找法改进,这种排序也被称为二分插入排序。
附二分查找代码,完成插入操作小改即可。

#include <stdlib.h>
#include <string.h>
#include "search.h"
/*bisearch 二分查找函数*/
int bisearch(void *sorted, const void *target, int size, int esize,int (*compare)(const void *key1, const void key2))
{int left, middle, right;/*初始化left和right为边界值*/left = 0;right = size - 1; /*循环查找,直到左右两个边界重合*/while(left<=right){middle = (left + right) / 2;switch(compare(((char *)sorted + (esize * middle)),target)){case -1: /*middle小于目标值*//*移动到middle的右半区查找*/left = middle + 1;break;case 1:  /*middle大于目标值*//*移动到middle的左半区查找*/right = middle - 1;break;case 0:  /*middle等于目标值*//*返回目标的索引值middle*/return middle;}}/*目标未找到,返回-1*/return -1;
}

2. 希尔排序

2.1 原理

先取一个小于n的整数d1作为第一个增量,把数据分组。所有距离为d1的倍数的数据放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量 =1( < …<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
该方法实质上是一种分组插入方法。

2.2 算法描述

我们来看下希尔排序的基本步骤,在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处我们做示例使用希尔增量。

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

2.3 动画演示

2.4 相关代码

void shell_sort(int array[], int length){int i;int j;int k;int gap;  //gap是分组的步长int temp;    //希尔排序是在直接插入排序的基础上实现的,所以仍然需要哨兵for(gap=length/2; gap>0; gap=gap/2){for(i=0; i<gap; i++){for(j=i+gap; j<length; j=j+gap){   //单独一次的插入排序if(array[j] < array[j - gap]){temp = array[j];   //哨兵k = j - gap;while(k>=0 && array[k]>temp){array[k + gap] = array[k];k = k - gap;}array[k + gap] = temp;}}}}
}

2.5 算法改进

希尔排序已经是直接插入排序的优化改进方案,在使用希尔排序时,需要选择合适的增量序列作为排序辅助,而这也是一个比较复杂的抉择。所以希尔排序在实际使用排序时并不常用。

3.简单选择排序

3.1 原理

对比数组中前一个元素跟后一个元素的大小,如果后面的元素比前面的元素小则用一个变量k来记住他的位置,接着第二次比较,前面“后一个元素”现变成了“前一个元素”,继续跟他的“后一个元素”进行比较如果后面的元素比他要小则用变量k记住它在数组中的位置(下标),等到循环结束的时候,我们应该找到了最小的那个数的下标了,然后进行判断,如果这个元素的下标不是第一个元素的下标,就让第一个元素跟他交换一下值,这样就找到整个数组中最小的数了。然后找到数组中第二小的数,让他跟数组中第二个元素交换一下值,以此类推。

简单地说,第一次循环就是找到所有数据中最小的,并把它放到第一个位置,第二次循环就是找到从第二个位置到最后一个位置中最小的,放到第二个位置,以此类推来确定所有数据的位置。

3.2 算法描述

n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:
步骤1:初始状态:无序区为R[1…n],有序区为空;
步骤2:第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
步骤3:n-1趟结束,数组有序化了。

3.3 动画演示

3.4 相关代码

#include<stdio.h>
void main()//主函数
{int a[10];int i,j,w;printf("请输入10个数字: \n");for(i=0;i<10;i++)scanf("%d",&a[i]);for(i=0;i<10;i++){for(j=0;j<10;j++)if(a[i]<a[j])//进行比较
//比较后进行交换
{w=a[i];a[i]=a[j];a[j]=w;
}}
printf("排序后:\n");for(i=0;i<10;i++)printf("%4d",a[i]);printf("\n");}

3.5 算法改进

二元选择排序
简单选择排序,每趟循环只能确定一个元素排序后的定位。我们可以考虑改进为每趟循环确定两个元素(当前趟最大值和最小值)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环即可。具体实现如下:

void SelectSort(int *arr,size_t size)
{size_t minIndex, maxIndex;for (size_t i = 0; i < size / 2; i++) {minIndex = i;maxIndex = size-i-1;for (size_t j = i; j < size - i ; j++) {if (arr[j] > arr[maxIndex]) {maxIndex = j;//continue;}if (arr[j] < arr[minIndex]) {minIndex = j;}}if (maxIndex == i && minIndex == size - i - 1) {//特殊交换位置一,当最大的交换位置和最小的交换位置都在最前面和最后面  swap(arr[maxIndex],arr[minIndex]);}else if (maxIndex == i) {//特殊交换位置二,当最大的交换位置是最前面  // Swap(arr[n - i], arr[maxIndex])swap(arr[size-i-1],arr[maxIndex]);swap(arr[i], arr[minIndex]);}else if (minIndex == size - i - 1) {//特殊交换位置三,当最小的交换位置是最后面  swap(arr[i], arr[minIndex]);swap(arr[size - i-1], arr[maxIndex]);}else {//除了上面三种特殊交换,就剩普通交换了,普通交换随便哪个先交换都行  swap(arr[i], arr[minIndex]);swap(arr[size - i-1], arr[maxIndex]) ;  }}
}

4. 堆排序

4.1 原理

堆排序 是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

关于堆的知识点
1.堆是一个完全二叉树
2.完全二叉树即是:若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
3.堆满足两个性质: 堆的每一个父节点数值都大于(或小于)其子节点,堆的每个左子树和右子树也是一个堆。
4.堆分为最小堆和最大堆。最大堆就是每个父节点的数值要大于孩子节点,最小堆就是每个父节点的数值要小于孩子节点。排序要求从小到大的话,我们需要建立最大堆,反之建立最小堆。
5.堆的存储一般用数组来实现。假如父节点的数组下标为i的话,那么其左右节点的下标分别为:(2i+1)和 (2i+2)。如果孩子节点的下标为j的话,那么其父节点的下标为(j-1)/2。
6.完全二叉树中,假如有n个元素,那么在堆中最后一个父节点位置为(n/2-1)。

4.2 算法描述

步骤1:将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
步骤2:将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
步骤3:由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

4.3 动画演示

4.4 相关代码

#include<stdio.h>int a[100];
void Exchange(int i,int n)//将最大叶子结点与根结点交换
{int temp;temp = a[i];a[i] = a[n];a[n] = temp;}void Created(int n)
{if(n == 2){//当只剩两个的时候,直接比较大小 先输出最大的 然后输出最小的 if(a[0] > a[1]){printf("%d %d\n",a[0],a[1]);}elseprintf("%d %d\n",a[1],a[0]);return;}int i;for(i = n/2-1;i >= 0; i--) //比较每一个根结点与其叶子结点的大小   并且将大于根结点的叶子结点与根结点交换 {//最后一个根结点是第n/2个 但是第一个数下标为0  所以最后一个根结点下标为n/2-1 if(a[2*i+1] > a[i] && 2*i+1 < n)//与左孩子进行比较 Exchange(i,2*i+1);if(a[2*i+2] > a[i] && 2*i+2 < n)//与右孩子进行比较 Exchange(i,2*i+2);}int t;t = a[0];//将第一个根结点与最后一个叶子结点互换 a[0] = a[n-1];a[n-1] = t;printf("%d ",a[n-1]);//输出最大的结点 Created(n-1);//将是输出的结点弹出
}int main()
{int  n;printf("请输入需要排序的元素的个数:");scanf("%d",&n);printf("请输入%d个元素:",n);for(int j =0; j <n; j++){scanf("%d",&a[j]);}Created(n);return 0;
}

4.5 算法改进

思想:
1.建立大堆;
2.取堆顶元素和堆尾元素交换;(此时,大堆已破坏,需要重新往下调整,恢复大堆)
3.恢复大堆前,需要减掉已经在正确位置的堆尾元素

5. 冒泡排序

5.1 原理

名字非常形象,冒泡排序的思想就是每一趟排序都把大的元素往上浮。它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

5.2 算法描述

步骤1: 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
步骤2: 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
步骤3: 针对所有的元素重复以上的步骤,除了最后一个;
步骤4: 重复步骤1~3,直到排序完成。

5.3 动画演示

5.4 相关代码

#include <stdio.h>
//交换 a 和 b 的位置的函数
void swap(int *a, int *b);
int main()
{int array[8] = {49,38,65,97,76,13,27,49};int i, j;int key;//有多少记录,就需要多少次冒泡,当比较过程,所有记录都按照升序排列时,排序结束for (i = 0; i < 8; i++){key=0;//每次开始冒泡前,初始化 key 值为 0//每次起泡从下标为 0 开始,到 8-i 结束for (j = 0; j+1<8-i; j++){if (array[j] > array[j+1]){key=1;swap(&array[j], &array[j+1]);}}//如果 key 值为 0,表明表中记录排序完成if (key==0) {break;}}for (i = 0; i < 8; i++){printf("%d ", array[i]);}return 0;
}
void swap(int *a, int *b){int temp;temp = *a;*a = *b;*b = temp;
}

5.5 算法改进

有时经过一趟排序,并没有发生任何交换过程,说明此时已经有序了,不需要进行下一次循环了。
改进方式有三种,在这不一一列举,推荐大家看看:https://blog.csdn.net/hansionz/article/details/8082249 里面很详细

6. 快速排序

6.1 原理

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

6.2 算法描述

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
步骤1:从数列中挑出一个元素,称为 “基准”(pivot );
步骤2:重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
步骤3:递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

6.3 动画演示

6.4 相关代码

#include <stdio.h>int getStandard(int array[],int low,int high){//基准数据 int key=array[low];while (low < high) {//因为默认基准是从左边开始,所以从右边开始比较 //当队尾的元素大于等于 基准数据 时,就一直向前挪动high指针 while (low < high && array[high] >= key) { high--; }//当找到比 array[low] 小的时,就把后面的值 array[high] 赋给它 if(low<high){array[low] = array[high];  }//当队首元素小于等于 基准数据 时,就一直向后挪动low指针 while (low < high && array[low] <= key) { low++; }//当找到比 array[high] 大的时,就把前面的值 array[low] 赋给它if(low<high){array[high] = array[low];   } } //跳出循环时low和high相等,此时的low或high就是key的正确索引位置//把基准数据赋给正确位置 array[low] = key;   return low;
} void quickSort(int array[],int low,int high){//开始默认基准为    low=0if(low<high){//分段位置下标 int standard=getStandard(array,low,high);//递归调用排序//左边排序 quickSort(array, 0, standard - 1);//右边排序 quickSort(array, standard + 1, high);}
}int main(){int array[]={49,38,65,97,76,13,27,49,10};int size= sizeof(array) / sizeof(int);printf("%d \n",size);quickSort(array,0,size-1);printf("排序的数组:");for(int i=0;i<size;i++){printf("%d ",array[i]);}printf("\n");return 0;
}

6.5 算法优化

详见https://blog.csdn.net/weixin_41162823/article/details/79938418

7.归并排序

7.1原理

归并排序 是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

7.2 算法描述

步骤1:把长度为n的输入序列分成两个长度为n/2的子序列;
步骤2:对这两个子序列分别采用归并排序;
步骤3:将两个排序好的子序列合并成一个最终的排序序列。

7.3 动画演示

7.4 相关代码

//分组归并
void _Merge(int *a, int begin1, int end1, int begin2, int end2, int *tmp)
{int index = begin1;int i = begin1, j = begin2;//注意:当划分的区间足够小时,begin1==end1,begin2==end2while (i <= end1&&j <= end2){if (a[i]<=a[j])tmp[index++] = a[i++];elsetmp[index++] = a[j++];}//将左边元素填充到tmp中while (i <= end1)tmp[index++] = a[i++];//将右边元素填充的tmp中while (j <= end2)tmp[index++] = a[j++];//将tmp中的数据拷贝到原数组对应的序列区间//注意:end2-begin1+1memcpy(a + begin1, tmp + begin1, sizeof(int)*(end2 - begin1 + 1));
}
//归并排序
void MergeSort(int *a, int left, int right, int *tmp)
{if (left >= right)return;assert(a);//mid将数组二分int mid = left + ((right - left) >> 1);//左边归并排序,使得左子序列有序MergeSort(a, left, mid, tmp);//右边归并排序,使得右子序列有序MergeSort(a, mid + 1, right, tmp);//将两个有序子数组合并_Merge(a, left, mid, mid + 1, right, tmp);
}

7.5 算法优化

①、直接将辅助数组作为参数传入,而不直接使用静态数组。
②、对小规模子数组使用插入排序,一般可以将归并排序的时间缩短 10% ~ 15%;
③、判断测试数组是否已经有序,如果 arr[mid] <= arr[mid+1],我们就认为数组已经是有序的并跳过merge() 方法,可以是任意有序的子数组算法的运行时间变为线性的。
④、merge() 方法中不将元素复制到辅助数组,节省数组复制的时间。调用两种排序方法,一种:将数据从输入数组排序到辅助数组;另一种:将数据从辅助数组排序到输入数组。
重点:在每个层次交换输入数组和辅助数组的角色。

8.计数排序

8.1 原理

计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(nlog(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(nlog(n)), 如归并排序,堆排序)

8.2 算法描述

步骤1:找出待排序的数组中最大和最小的元素;
步骤2:统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
步骤3:对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
步骤4:反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

8.3 动画演示

8.4 相关代码

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
//计数排序
void CountSort(int *a, int len)
{assert(a);//通过max和min计算出临时数组所需要开辟的空间大小int max = a[0], min = a[0];for (int i = 0; i < len; i++){if (a[i] > max)max = a[i];if (a[i] < min)min = a[i];}//使用calloc将数组都初始化为0int range = max - min + 1;int *b = (int *)calloc(range, sizeof(int));//使用临时数组记录原始数组中每个数的个数for (int i = 0; i < len; i++){//注意:这里在存储上要在原始数组数值上减去min才不会出现越界问题b[a[i] - min] += 1;}int j = 0;//根据统计结果,重新对元素进行回收for (int i = 0; i < range; i++){while (b[i]--){//注意:要将i的值加上min才能还原到原始数据a[j++] = i + min;}}//释放临时数组free(b);b = NULL;
}
//打印数组
void PrintArray(int *a, int len)
{for (int i = 0; i < len; i++){printf("%d ", a[i]);}printf("\n");
}
int main()
{int a[] = { 3, 4, 3, 2, 1, 2, 6, 5, 4, 7 };printf("排序前:");PrintArray(a, sizeof(a) / sizeof(int));CountSort(a, sizeof(a) / sizeof(int));printf("排序后:");PrintArray(a, sizeof(a) / sizeof(int));system("pause");return 0;
}

9. 桶排序

9.1 原理

桶排序 是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。
桶排序 (Bucket sort)的工作的原理:
假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序。

9.2 算法描述

步骤1:人为设置一个BucketSize,作为每个桶所能放置多少个不同数值(例如当BucketSize==5时,该桶可以存放{1,2,3,4,5}这几种数字,但是容量不限,即可以存放100个3);
步骤2:遍历输入数据,并且把数据一个一个放到对应的桶里去;
步骤3:对每个不是空的桶进行排序,可以使用其它排序方法,也可以递归使用桶排序;
步骤4:从不是空的桶里把排好序的数据拼接起来。

9.3 动画演示

9.4 相关代码

#include<stdio.h>
int main()
{printf("please enter:\n");int a[100],t,max=sizeof(a)/sizeof(int);for(int i=0;i<max;i++)a[i]=0;for(int j=0;j<5;j++){scanf("%d",&t);a[t]++;}for(int k=0;k<max;k++)for(int y=0;y<a[k];y++)printf("%d ",k);return 0;
}

10. 基数排序

10.1 原理

基数排序也是非比较的排序算法,对每一位进行排序,从最低位开始排序,复杂度为O(kn),为数组长度,k为数组中的数的最大的位数;

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

10.2 算法分析

步骤1:取得数组中的最大数,并取得位数;
步骤2:arr为原始数组,从最低位开始取每个位组成radix数组;
步骤3:对radix进行计数排序(利用计数排序适用于小范围数的特点);

10.3 动画演示

10.4 相关代码

#include <stdio.h>
#include <stdlib.h>  void RadixCountSort(int *index,int *a,int len)
{  int i;  int *count=(int *)malloc(sizeof(int)*10);  for(i=0;i<10;i++)  {  count[i]=0;  }  for(i=0;i<len;i++)  {  count[index[i]] ++;  }  for(i=1;i<10;i++)  {  count[i]=count[i] + count[i - 1];  }  int *sort=(int *)malloc(sizeof(int)*len);  for(i=len-1;i>=0;i--)  {  count[index[i]] --;  sort[count[index[i]]] = a[i];  }  for(i=0;i<len;i++)  {  a[i]=sort[i];  }  free(sort);  free(count);
}  void RadixSort(int *a,int len)
{  int i , x=1;  int tmp=1;  int *radix=(int *)malloc(sizeof(int)*len);  while(x)  {  tmp=tmp * 10;  x=0;  for(i=0;i<len;i++)  {  radix[i]=a[i] % tmp;  radix[i]=radix[i] / (tmp / 10);  if(a[i] / tmp > 0)  {  x = 1;  }  }  RadixCountSort(radix,a,len);  }  free(radix);
}  int main()
{  int i,len;  int a[]={100, 8 , 1099, 6, 1, 300, 405, 604, 102, 806, 706, 504};  len =sizeof(a) / sizeof(int);  RadixSort(a,len);  for(i=0;i<len;i++)  {  printf("%d ",a[i]);  }  printf("\n");  return 0;
}

十大经典排序算法(附代码、动画及改进方案)相关推荐

  1. 程序员面试必备:动图演示十大经典排序算法及代码实现

    0.算法概述 0.1 算法分类 十种常见排序算法可以分为两大类: 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序. 非比较类排序: ...

  2. 11月14日云栖精选夜读 | 动画+原理+代码,解读十大经典排序算法

    排序算法是<数据结构与算法>中最基本的算法之一. 排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过 ...

  3. C++实现桶排序——十大经典排序算法之九【GIF动画+完整代码+详细注释】

    十大经典排序算法系列博客-->传送门 桶排序是计数排序的升级版.它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定.桶排序 (Bucket sort)的工作的原理:假设输入数据服从均 ...

  4. 十大经典排序算法动画与解析,看我就够了

    作者 | 程序员小吴 转载自五分钟学算法(ID: CXYxiaowu) 排序算法是<数据结构与算法>中最基本的算法之一. 排序算法可以分为内部排序和外部排序.内部排序是数据记录在内存中进行 ...

  5. 十大经典排序算法及比较与分析 ( 动画演示 ) ( 可视化工具 )

    可视化工具及动画展示:旧金山大学 (usfca)|数据结构可视化工具 排序算法概念及描述:1.0 十大经典排序算法(文章部分内容引用自改文章) 参考:邓俊辉 的数据结构 本文未对排序算法概念进行详细说 ...

  6. 【完整可运行源码+GIF动画演示】十大经典排序算法系列——冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序、桶排序、基数排序

    以前也零零碎碎发过一些排序算法,但总是不系统, 这次彻底的对排序系列做了一个整体的规划, 小伙伴们快快mark哦~ [GIF动画+完整可运行源代码]C++实现 冒泡排序--十大经典排序算法之一 [GI ...

  7. 【GIF动画+完整可运行源代码】C++实现 基数排序——十大经典排序算法之十

    十大经典排序算法系列博客-->传送门 基数排序是按照低位先排序,然后收集:再按照高位排序,然后再收集:依次类推,直到最高位.有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序.最 ...

  8. 【GIF动画+完整可运行源代码】C++实现 计数排序——十大经典排序算法之八

    十大经典排序算法系列博客-->传送门 计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中. 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确 ...

  9. 【GIF动画+完整可运行源代码】C++实现 堆排序——十大经典排序算法之七

    十大经典排序算法系列博客-->传送门 堆排序Heapsort是指利用堆这种数据结构所设计的一种排序算法.堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大 ...

  10. 【GIF动画+完整可运行源代码】C++实现 快速排序——十大经典排序算法之六

    十大经典排序算法系列博客-->传送门 ##### 1.快排的实现逻辑: 先从数列中取出一个数作为基准数(通常取第一个数). 遍历序列,将比它小的数与比它大的数分别记录下来,分为两类,最后该数放在 ...

最新文章

  1. 李超线段树(Li-Chao Segment Tree)
  2. python编程入门p-Python是什么?简单了解pythonp-入门
  3. asp服务器_200行代码,7个对象——让你了解ASP.NET Core框架的本质「3.x版」
  4. 冒险实施SAP HANA 农夫山泉缘何做第一个“吃螃蟹”者?
  5. python字符串字面量有哪四种定义方式_Python学习笔记(四)字符串型
  6. LeetCode LCP 06. 拿硬币
  7. 怎么用python画圆柱_python-如何绘制具有非恒定半径的圆柱
  8. jenkins 集成java搅拌_java-Jenkins中的集成测试
  9. WCF中使用自定义behavior提示错误的解决方法
  10. 给自己做个文件的保险箱
  11. 机房服务器配置方案文件,机房改造/机房搬迁实施方案及步骤
  12. 微软Windows字体被诉侵权?我们来聊聊有关网站侵权被诉的那些事。
  13. SPSS如何进行Cox回归分析操作
  14. 拍摄制作360度全景图有哪些技巧?
  15. 操作性条件作用和经典性条件作用中,刺激分化和泛化的区别是?|小白心理-312/347考研答疑
  16. 什么录音软件可以录制电影对白
  17. 风变编程python18_如何看待风变编程的 Python 网课?
  18. 企业信用资质等级证书的办理流程
  19. Linux常用命令,Linux常用命令整理
  20. 荣誉系统排名是整个服务器,魔兽世界60年代的PVP荣誉系统、军阶对照表和各职业的徽记效果...

热门文章

  1. 使用yield返回IEnumberT集合
  2. ORA-00906 missing left parenthesis括号
  3. 机器学习 —— 极大似然估计与条件概率
  4. 转载: CentOS下配置Apache
  5. SharePoint 关于拓扑错误的解决方案
  6. C# 类型参数的约束
  7. UNIX环境高级编程——线程同步之条件变量以及属性
  8. java内存模型之二volatile内存语义
  9. cocos creator基础-(十三)cc.Loader使用
  10. WebApi实现验证授权Token,WebApi生成文档等