作为一个程序猿,编写程序,排序算法算是最基本的了。这篇博客中,我尽量以最通俗易懂的语言,最简单的代码来讲清楚这些比较常见的排序算法。
好了,首先来看一下各种排序算法的效率和稳定性方面的总结(这张图很好,网上找的):
下面我就根据这张图来说明各种排序算法。
一.插入排序
1.直接插入排序
直接插入排序就是把后面的元素依次插入到前面的有序列中,(第一次以第一个元素为有序列),插入位置后面的元素后移。下面是代码.
#include<stdio.h>
int main()
{
int sqList[]= {1,3,6,7,2,4,4,0,9,8};
insertSortDirect(sqList,10);
printArr(sqList, 10);
return 0;
}
void insertSortDirect(int *sqList, int length)
{
int i;
for(i = 1; i < length; i++)//第一次把下标为0的作为有序序列
{
int temp = sqList[i];
int j;
for(j = i; j > 0; j--)
{
if(sqList[j - 1] > temp)
{
sqList[j] = sqList[j - 1];
}
else
break;
}
sqList[j] = temp;
}
}
void printArr(int *sqList, int length)
{
int i;
for(i = 0; i < length; i++)
{
printf("%d ", sqList[i]);
}
}
2.shell排序
shell排序其实就是直接插入排序的扩展。举个例子,shell排序先将待排数组以3为间隔分为几个子数组,对这几个子数组进行直接插入排序,然后以2为间隔重复,最后以1为间隔重复(此时就是直接插入排序)。因为对基本有序的数组进行直接插入排序效率很高。下面是代码。
#include<stdlib.h>
void shellInsert(int *sqList, int length, int increment)
{
int i;
for(i = increment; i < length; i++)
{
int temp = sqList[i];
int j;
for(j = i; j >= increment; j = j - increment)//不是把愿数组真的分为几个子数组,因为每一次的shellInsert的步长是固定的,所以可以这样
{
if(temp < sqList[j - increment])
{
sqList[j] = sqList[j - increment];
}
else
break;
}
sqList[j] = temp;
}
}
void shellSort(int *sqList, int length1, int *dlta, int length2)
{
int i;
for(i = 0; i < length2; i++)
{
shellInsert(sqList, length1, dlta[i]);
}
}
void printArr(int *sqList, int length)
{
int i;
for(i = 0; i < length; i++)
{
printf("%d ", sqList[i]);
}
}
int main()
{
int sqList[]= {1,3,6,7,2,4,4,0,9,8};
int dlta[] = {3, 2, 1};
shellSort(sqList, 10, dlta, 3);
printArr(sqList, 10);
return 0;
}
扩展几个:
1.折半插入排序
在直接插入排序时,用二分查找法来寻找待插入的位置。
#include <stdio.h>
void insertSortB(int *sqList, int length)
{
int i;
for(i = 1; i < length; i++)
{
int temp = sqList[i];
int max = i - 1;
int min = 0;
while(min <= max)
{
int center = (max + min)/2;
if( temp < sqList[center])
max = center - 1;
else
min = center + 1;
}
int j;
for(j = i - 1; j >= max + 1; j--)
{
sqList[j + 1] = sqList[j];
}
sqList[max + 1] = temp;
}
}
void printArr(int *sqList, int length)
{
int i;
for(i = 0; i < length; i++)
{
printf("%d ", sqList[i]);
}
}
int main()
{
int sqList[]= {1,3,6,7,2,4,4,0,9,8};
insertSortB(sqList, 10);
printArr(sqList, 10);
return 0;
}
2.2-路插入排序
3.表插入排序
二.选择排序
1.直接选择排序
直接选择排序就是每次寻找最大(小)的元素位置,然后将此元素与紧跟在有序列后面的第一个元素交换。直到有序列等于数组长度。虽然有交换,但是是基于选择的。
#include<stdlib.h>
void printArr(int *sqList, int length)
{
int i;
for(i = 0; i < length; i++)
{
printf("%d ", sqList[i]);
}
}
int selectMin(int *sqList, int low, int high)
{
int min = sqList[low];
int minLocation = low;
int i;
for(i = low; i < high; i++)
{
if(sqList[i] < min)
{
min = sqList[i];
minLocation = i;
}
}
return minLocation;
}
void simpleSelectSort(int *sqList, int length)
{
int i;
for(i = 0; i < length; i++)
{
int temp = sqList[i];
int minLocation = selectMin(sqList, i, length);
sqList[i] = sqList[minLocation];
sqList[minLocation] = temp;
}
}
int main()
{
int sqList[]= {1,3,6,7,2,4,4,0,9,8};
simpleSelectSort(sqList, 10);
printArr(sqList, 10);
return 0;
}
2.堆排序
堆排序也很简单,原理很容易懂,主要是循环的结束判断用到了完全二叉树的性质。其实二叉树的性质大家也不用死记硬背,用的时候画一颗树推一下就好了。下面是代码,很容易看懂。
#include<stdio.h>
void print(int *sqList, int length)
{
int i;
for(i = 0; i < length; i++)
{
printf("%d ", sqList[i]);
}
}
//m为调整的开始点,n为m堆得节点的最大下标
void heapAdjust(int *sqList, int i, int n)
{
int index = i;
int temp = sqList[i];
i = 2 * i + 1;
while(i <= n)
{
if(i + 1 <= n && sqList[i] < sqList[i + 1])
i++;
if(temp >= sqList[i])
break;
sqList[index] = sqList[i];
sqList[i] = temp;
index = i;
temp = sqList[i];
i = 2 * i + 1;
}
}
//从倒数第二层的第一个元素开始依次向上,对每个节点进行调整
// 0
// / \
// 1 2
// / \ / \
// 3 4 5 6
// /
// 7
//所以构造最大堆时,调整函数的参数n可以一直是数组长度
void creatHeap(int *sqList, int length)
{
int i;
for(i = (length - 1) / 2; i >= 0; i--)
heapAdjust(sqList, i, length - 1);
}
int main()
{
int sqList[] = {65, 0, 78, 18, 4, 66, 97};
creatHeap(sqList, 7);
int temp = sqList[0];
sqList[0] = sqList[6];
sqList[6] = temp;
int i;
for(i = 5; i >= 0; i--)
{
heapAdjust(sqList, 0, i);
temp = sqList[0];
sqList[0] = sqList[i];
sqList[i] = temp;
}
print(sqList, 7);
}
三.交换排序
1.冒泡排序
每趟冒泡的过程,就是不断交换相邻元素,以此把最大(小)的元素交换到有序列后面。
#include<stdlib.h>
void printArr(int *sqList, int length)
{
int i;
for(i = 0; i < length; i++)
{
printf("%d ", sqList[i]);
}
}
void swap(int *sqList, int i, int j)
{
sqList[i] = sqList[i] + sqList[j];
sqList[j] = sqList[i] - sqList[j];
sqList[i] = sqList[i] - sqList[j];
}
void bubbleSort(int *sqList, int length)
{
int i;
for(i = 0; i < length; i++)
{
int j;
for(j = 0; j < length - i -1; j++)
{
if(sqList[j] > sqList[j + 1])
swap(sqList, j, j + 1);
}
}
}
int main()
{
int sqList[]= {1,3,6,7,2,4,4,0,9,8};
bubbleSort(sqList, 10);
printArr(sqList, 10);
return 0;
}
2.快速排序
快速排序就是每次选择一个元素,然后把比他大的放前面,比他小的放后面,他放中间。递归(或循环)直到子数组只有一个元素。
#include<stdlib.h>
void printArr(int *sqList, int length)
{
int i;
for(i = 0; i < length; i++)
{
printf("%d ", sqList[i]);
}
}
int partition(int *sqList, int low, int high)
{
int temp = sqList[low];
while(low < high)
{
//第一个判断是边界的检验
while(low < high && sqList[high] >= temp) high--;
sqList[low] = sqList[high];
while(low < high && sqList[low] <= temp) low++;
sqList[high] = sqList[low];
}
sqList[low] = temp;
return low;
}
void quickSort(int *sqList, int low, int high)
{
if(low < high)
{
int mid = partition(sqList, low, high);
quickSort(sqList, low, mid -1);
quickSort(sqList, mid + 1, high);
}
}
int main()
{
int sqList[]= {1,3,6,7,2,4,4,0,9,8};
quickSort(sqList, 0, 9);
printArr(sqList, 10);
return 0;
}
四.归并排序
归并排序是分治法应用的很好例子。所谓分治法,就是把原问题分为规模较小,问题一样的子问题,不断分割知道子问题很容易就解出,记录子问题的结果,然后合并为原问题的解。下面是代码,很容易就可以看懂其中的过程。
#include<stdio.h>
void print(int *sqList, int length)
{
int i;
for(i = 0; i < length; i++)
{
printf("%d ", sqList[i]);
}
}
void merge(int *sqList, int first, int mid, int last, int *temp)
{
int m = first;
int i = first;
int j = mid + 1;
while(i <= mid && j <= last)
{
if(sqList[i] < sqList[j])
{
temp[m] = sqList[i];
i++;
m++;
}
else
{
temp[m] = sqList[j];
j++;
m++;
}
}
if(i <= mid)
{
for(i; i <= mid; i++)
{
temp[m] = sqList[i];
m++;
}
}
if(j <= last);
{
for(j; j <= last; j++)
{
temp[m] = sqList[j];
m++;
}
}
for(i = first; i <= last; i++)
sqList[i] = temp[i];
}
void mergingSort(int *sqList, int begin, int end, int *temp)
{
if(begin == end)
temp[begin] = sqList[begin];
else
{
int i = (begin + end) / 2;
mergingSort(sqList, begin, i, temp);
mergingSort(sqList, i + 1, end, temp);
merge(sqList, begin, i, end, temp);
}
}
int main()
{
int sqList[] = {65, 70, 78, 18, 19, 66, 97};
int temp[7] = {};
mergingSort(sqList, 0, 6, temp);
print(sqList, 7);
}
五.基数排序
基数排序是基于多关键字的排序,k0,k1,k2,k3....kn,(一个关键字可以拆分为几个关键字,比如对数字排序,个十百可以分别作为关键字进行基数排序)。
基数排序可以分为两种:
1.从主关键字开始,即MSD基数排序
这种方法根据关键字的主次顺序依次把数组分为子数组,等到拆分到只剩最次关键字时,排序即可。
2.从最次关键字开始,即LSD基数排序。但对k i(0 <= i <= n - 1)排序时,只能用稳定的排序算法。
这种方法是从最次关键字开始,进行若干次分配,收集。下面以例子进行说明。
#include<stdio.h>
/**
*这里用数组模拟链表
*当然也可以使用真实的链表
*/
/**
*对3位整数进行排序,则有三个关键字:个十百。
*每个关键字的基数都是0-9,所以有3次分配收集的过程。
*每一次分配,需要有一个包含10个元素的数组,用来记录每个基数对应的链表(逻辑上的)的头元素的开始下标
*/
//求出数字的个十或百位的数字作为关键字
//location取值为1,2,3.分别对应个十百
int getKey(int data, int location)
{
int key;
int i;
for(i = 0; i < location; i++)
{
key = data % 10;
data = data / 10;
}
return key;
}
typedef struct
{
//因为data里包含了关键字数组,所以不用关键字数组了。
int data;
int next;
}SLCell;
//分配的算法
void distribute(SLCell *sqList, int length, int *baseBegin, int *baseEnd, int keyLocation)
{
int key;//key其实也是这个数在base数组中的下标
int i;
for(i = 0; i < length; i++)
{
key = getKey(sqList[i].data, keyLocation);
if(baseBegin[key] == -1)
{
baseBegin[key] = i;
baseEnd[key] = i;
}
else
{
sqList[baseEnd[key]].next = i;
baseEnd[key] = i;
}
}
}
//收集的算法
void collect(SLCell *sqList, int length, int *baseBegin)
{
int index = 0;
SLCell sqCell;
SLCell sqListTemp[1000];//c不能声明为length长度
int i;
for(i = 0; i < 10; i++)
{
if(baseBegin[i] != -1)
{
int flag = 1;
sqCell = sqList[baseBegin[i]];
baseBegin[i] = -1;//为下一次重新分配做好准备(还原)
while(flag == 1)
{
sqListTemp[index].data = sqCell.data;
sqListTemp[index].next = -1;baseBegin[i]
index++;
if(sqCell.next == -1)
flag = 0;
else
sqCell = sqList[sqCell.next];
}
}
}
for(i = 0; i < length; i++)
{
sqList[i].data = sqListTemp[i].data;
sqList[i].next = -1;
}
}
int main()
{
/
//构造一个待排序的链表
SLCell sqList[11] = {};
int datas[] = {789, 123, 780, 123, 345, 321, 124, 98, 567 , 190,111};
int i;
for(i = 0; i < 11; i++)
{
sqList[i].data = datas[i];
sqList[i].next = -1;
}
int baseBegin[10] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
int baseEnd[10] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
for(i = 1; i <= 3; i++)
{
distribute(sqList, 11, baseBegin, baseEnd, i);
collect(sqList, 11, baseBegin);
}
for(i = 0; i < 11; i++)
{
printf("%d ", sqList[i]);
}
}