最近在学习数据结构与算法,结合网上资源整理了10个经典排序算法,以供大家学习C语言编程算法。
排序算法 | 平均时间复杂度 | 最差时间复杂度 | 空间复杂度 | 数据对象稳定性 |
---|---|---|---|---|
冒泡排序 | O() | O() | O(1) | 稳定 |
选择排序 | O() | O() | O(1) | 数组不稳定、链表稳定 |
插入排序 | O() | O() | O(1) | 稳定 |
快速排序 | O() | O() | O() | 不稳定 |
堆排序 | O() | O() | O(1) | 不稳定 |
归并排序 | O() | O() | O(n) | 稳定 |
希尔排序 | O() | O() | O(1) | 不稳定 |
计数排序 | O(n+m) | O(n+m) | O(n+m) | 稳定 |
桶排序 | O(n) | O(n) | O(m) | 稳定 |
基数排序 | O(d(n+r)) | O(d(n+r)) | O(r) | 稳定 |
1 冒泡排序
算法思想:
- 从后往前两两比较相邻元素的值,若为逆序(A[i-1]>A[i]),则交换它们,直到序列比较完,称为一趟冒泡。
- 每趟冒泡的结果是把序列中最小的元素放到了序列的最终位置。
- 下一趟冒泡时,前一趟确定的最小元素不再参与比较,待排序列减少一个元素。
- 重复上述冒泡过程,若一趟冒泡过程中,没有元素交换,则完成排序。
代码:
// 冒泡排序
void BubbleSort(int A[], int n){
int flag = 0;
//用冒泡排序法将序列A中的元素按小到大排列
for(int i = 0; i -1 ; i++){
flag = 0;
for(int j = n-1; j > i; j--){
if(A[j-1] > A[j]){
int temp = A[j-1]; // 交换
A[j-1] = A[j];
A[j] = temp;
flag = 1;
}
}
if(flag == 0)
return;
}
}
2 选择排序
算法思想:
- 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
- 从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
- 以此类推,直到所有元素均排序完毕。
代码:
// 选择排序
void SelectionSort(int A[], int n){
int minIndex, temp;
for(int i = 0; i -1; i++){
minIndex = i;
for(int j = i + 1; j if(A[j] // 寻找最小的数
minIndex = j; // 将最小数的索引保存
}
}
if(minIndex != i){
temp = A[i];
A[i] = A[minIndex];
A[minIndex] = temp;
}
}
return;
}
3 插入排序
3.1 直接插入排序
算法思想:
- 从第一个元素开始,该元素可以认为已经被排序。
- 取出下一个元素,在已经排序的元素序列中从后向前扫描。
- 如果该元素(已排序)大于新元素,将该元素移到下一位置。
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置。
- 将新元素插入到该位置后。
- 重复步骤2~5。
代码:
// 直接插入排序
void InsertSort(int A[], int n){
int i, j;
int x; // 哨兵
for(int i = 1; i //依次将A[1]~A[n]插入到前面已排序序列
if(A[i] -1]){ //若A[i]小于其前驱,则需要将A[i]插入有序表
x = A[i]; //复制为哨兵
for(j = i-1; x //从后往前查找待插入位置
A[j+1] = A[j]; //向后挪位
A[j+1] = x; //先后移一个元素
}
}
}
3.2 折半插入排序
代码:
// 折半插入排序
void InsertSort(int A[], int n){
int i, j, low, high, mid;
int x; // 哨兵
for(int i = 1; i //依次将A[1]~A[n]插入到前面已排序序列
x = A[i];
low = 0;
high = i-1;
while(low <= high){
mid = (low + high)/2;
if(A[mid] > x) high = mid - 1;
else low = mid + 1;
}
for(j = i-1; j >= high; --j)
A[j+1] = A[j]; //向后挪位
A[high+1] = x; //先后移一个元素
}
}
4 快速排序
算法思想:
- 选取第一个数为枢纽。
- 将比枢纽小的数交换到左端,比枢纽大的数交换到右端。
- 对左右区间重复第二步,直到各区间只有一个数。
代码:
// 快速排序
int Partition(int A[], int low, int high){
int pivot = A[low]; // 将当前表的第一个元素设为枢纽值,对标进行划分
while(low // 循环跳出条件
while(low = pivot) --high;
A[low] = A[high]; // 将比枢纽值小的元素移到左端
while(low A[high] = A[low]; // 将比枢纽值大的元素移到右端
}
A[low] = pivot;
return low;
}
void QuickSort(int A[], int low, int high){
if(low //递归跳出的条件
int pivotpos = Partition(A, low, high); // 划分
QuickSort(A, low, pivotpos-1); // 依次对两个子表进行递归排序
QuickSort(A, pivotpos+1, high);
}
}
5 堆排序
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
算法思想:
- 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区。
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n]。
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
代码:
// 堆排序:(最大堆,有序区)。从堆顶把根卸出来放在有序区之前,再恢复堆。
void max_Heapify(int A[], int start, int end){
//建立父节点指标和子节点指标
int dad = start;
int son = dad * 2 + 1;
int temp;
while(son <= end){ //若子节点在范围内才做比较
if(son + 1 <= end && A[son] 1]) //先比较两个子节点指标,选择最大的
son++;
if(A[dad] > A[son]) //如果父节点大于子节点代表调整完成,直接跳出函数
return;
else{ //否则交换父子內容再继续子节点与孙节点比較
temp = A[dad];
A[dad] = A[son];
A[son] = temp;
dad = son;
son = dad * 2 + 1;
}
}
}
void HeapSort(int A[], int n){
//初始化,i从最后一个父节点开始调整
int temp;
for(int i = n / 2 - 1; i >= 0; i--)
max_Heapify(A, i, n - 1);
//先将第一个元素和已经排好的元素前一位做交换,再从新调整(刚调整的元素之前的元素),直到排序完成
for(int i = n - 1; i > 0; i--){
temp = A[0];
A[0] = A[i];
A[i] = temp;
max_Heapify(A, 0, i - 1);
}
}
6 归并排序
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法思想:
- 把长度为n的输入序列分成两个长度为n/2的子序列。
- 对这两个子序列分别采用归并排序。
- 将两个排序好的子序列合并成一个最终的排序序列。
代码:
// 归并排序
void Merge(int nums[], int tmpNums[], int lstart, int rstart, int rend){
int i = 0, lend = rstart - 1, tmpPos = lstart;
const int numSize = rend - lstart + 1;
while((lstart <= lend) && (rstart <= rend)){
if(nums[lstart] <= nums[rstart]){
tmpNums[tmpPos++] = nums[lstart++];
}else{
tmpNums[tmpPos++] = nums[rstart++];
}
}
while(lstart <= lend){
tmpNums[tmpPos++] = nums[lstart++];
}
while(rstart <= rend){
tmpNums[tmpPos++] = nums[rstart++];
}
for(i = 0; i nums[rend] = tmpNums[rend];
}
}
void Msort(int nums[], int tmpNums[], int left, int right){
int center = (left + right) / 2;
if (left Msort(nums, tmpNums, left, center);
Msort(nums, tmpNums, center + 1, right);
Merge(nums, tmpNums, left, center + 1, right);
}
}
void MergerSort(int nums[], int N){
int * tmpArray = NULL;
tmpArray = (int *)malloc(N * sizeof(int));
if(tmpArray != NULL){
Msort(nums, tmpArray, 0, N - 1);
free(tmpArray);
}
}
7 希尔排序
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序。
算法思想:
- 选择一个增量序列d1,d2,…,dk,其中di>dj(i
- 按增量序列个数k,对序列进行k 趟排序。
- 每趟排序,根据对应的增量di,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
代码:
// 希尔排序
void ShellSort(int A[], int n){
int temp, dk, i, j;
for(dk = n/2;dk > 0; dk = dk /2){
for(i = dk; i if(A[i] temp = A[i];
for(j = i - dk; j > -1 && temp A[j+dk] = A[j];
A[j+dk] = temp;
}
}
}
}
8 计数排序
计数排序统计小于等于该元素值的元素的个数i,于是该元素就放在目标数组的索引i位(i≥0)。
- 计数排序基于一个假设,待排序数列的所有数均为整数,且出现在(0,k)的区间之内。
- 如果 k(待排数组的最大值) 过大则会引起较大的空间复杂度,一般是用来排序 0 到 100 之间的数字的最好的算法,但是它不适合按字母顺序排序人名。
- 计数排序不是比较排序,排序的速度快于任何比较排序算法。
算法思想:
- 找出待排序的数组中最大和最小的元素。
- 统计数组中每个值为 i 的元素出现的次数,存入数组 C 的第 i 项。
- 对所有的计数累加(从 C 中的第一个元素开始,每一项和前一项相加)。
- 向填充目标数组:将每个元素 i 放在新数组的第 C[i] 项,每放一个元素就将 C[i] 减去 1。
代码:
// 计数排序
void CountSort(int A[],int n){
int max = A[0];//序列中的最大值
int min = A[0];//序列中的最小值
for(int i = 0;i if(A[i] >= max)
max = A[i];
if(A[i] <= min)
min = A[i];
}
int range = max - min + 1;//需要开辟的空间大小
int *count = (int*)malloc(sizeof(int)*(range));
memset(count,0,sizeof(int)*range);//辅助空间初始化为0,0代表没有那个数
for(int i = 0;i count[A[i] - min]++;//A[i]-min是将该数对应到辅助空间的下标
}
int index = 0;
for(int i = 0;i //遍历辅助空间
while(count[i]--){//下标处的数值是几,说明该数出现了几次
A[index++] = i + min;//将下标处的数对应回原数组
}
}
}
9 桶排序
将值为i的元素放入i号桶,最后依次把桶里的元素倒出来。
算法思想:
- 设置一个定量的数组当作空桶子。
- 寻访序列,并且把项目一个一个放到对应的桶子去。
- 对每个不是空的桶子进行排序。
- 从不是空的桶子里把项目再放回原来的序列中。
代码:
//桶排序
struct node{
int data;
struct node *next;
};
// 对每个链表(桶)进行插入排序
void insert_node(struct node **bucket, int data){
struct node *p = (struct node *)malloc(sizeof(struct node));
p->data = data;
p->next = NULL;
// 桶为空
if(*bucket == NULL){
*bucket = p;
}else{
struct node *pre = NULL;
struct node *cur = *bucket;
while(cur != NULL && cur->data <= data){
pre = cur;
cur = cur->next;
}
// 对插入到第一个结点前的情况处理
if(pre == NULL){
*bucket = p;
p->next = cur;
}else{
pre->next = p;
p->next = cur;
}
}
}
// k表示数据位数,3为表示取值范围[000-999]
void BucketSort(int a[], int length, int k){
// 申请桶空间
struct node **b = (struct node **)calloc(10,sizeof(struct node *));
int i,j,m;
// 将待排数据记录分配到桶
for(i=0; i // 获取对应10个桶的标识 0-9
m = a[i];
for(j=k; j>1; j--)
m = m/10;
// 分配到桶链表中
insert_node(&b[m],a[i]);
}
// 方便返回结果,复制到原数组a中
// 复制到数组a中
struct node *p;
for(i=0,j=0; i<10 && j if(b[i] != NULL){
p = b[i];
// 遍历每个桶元素
while(p != NULL){
a[j] = p->data;
j++;
p = p->next;
}
}
}
// 释放存储空
for(i=0; i<10; i++){
while(b[i] !=NULL){
p = b[i];
b[i] = p->next;
free(p);
}
}
free(b);
}
10 基数排序
一种多关键字的排序算法,可用桶排序实现。
算法思想:
- 取得数组中的最大数,并取得位数。
- A为原始数组,从最低位开始取每个位组成radix数组。
- 对radix进行计数排序(利用计数排序适用于小范围数的特点)。
代码:
// 基数排序
#define Max_ 10 //数组个数
#define RADIX_10 10 //整形排序
#define KEYNUM_31 31 //关键字个数,这里为整形位数
int GetNumInPos(int num,int pos){ // 找到num的从低到高的第pos位的数据
int temp = 1;
for(int i = 0; i 1; i++)
temp *= 10;
return (num / temp) % 10;
}
void RadixSort(int A[], int n){//基数排序
int *radixArrays[RADIX_10]; //分别为0~9的序列空间
for(int i = 0; i 10; i++){
radixArrays[i] = (int *)malloc(sizeof(int) * (n + 1));
radixArrays[i][0] = 0; //index为0处记录这组数据的个数
}
for(int pos = 1; pos <= KEYNUM_31; pos++){ //从个位开始到31位
for(int i = 0; i //分配过程
int num = GetNumInPos(A[i], pos);
int index = ++radixArrays[num][0];
radixArrays[num][index] = A[i];
}
for(int i = 0, j =0; i //收集
for(int k = 1; k <= radixArrays[i][0]; k++)
A[j++] = radixArrays[i][k];
radixArrays[i][0] = 0; //复位
}
}
}
PS:后台回复 “排序算法” 获取本篇文章PDF。
- End -