几种常见的排序算法
![](https://img-blog.csdnimg.cn/20190430212142629.jpg)
目录
一、算法思想简介
1、基数排序
排序算法中的一股泥石流!
以十进制为例,按LSD进行排序,即先排个位数字开,再排十位数字, ⋯ \cdots ⋯,直到最高位。
具体做法是:利用十个桶,进行若干次的分配和采集。
如果链表为空,则不需要排序;
否则,先寻找最大值及其位数,即分配采集的次数;
其次,将桶设置成空桶;
然后,开始分配和采集:
分配: 从链表中的第一个元素开始,读取其当前位上的数值,将其从原始链
表取下,放置到对应的桶的尾部,然后对后续元素进行相同处理;
采集: 直接首尾相连,一段接一段,具体实现是,遍历桶头,如果桶非空,
将其接到链表中,接完之后将桶再次赋为空并让该桶的尾指针指向桶
头,最终又得到一串序列;
经过若干次的分配采集,最终得到有序序列;
2、归并排序
非递归思想:假设初始序列有n个记录,将其看成是n个有序的子序列,每个子序列长度为1,然后两两归并,得到 ⌊ n / 2 ⌋ \lfloor n/2\rfloor ⌊n/2⌋个长度为2或1的有序子序列;再两两归并, ⋯ \cdots ⋯,如此重复下去,直到得到一个长度为n的有序序列。
递归思想:如果要归并排序的序列长度大于1,则进行平分,分别对两个子序列进行归并排序,即调用递归函数,然后归并得到的两个有序子序列。
3、冒泡排序
简单的冒泡排序思想不再赘述,改进思路:判别冒泡排序结束的条件应该是“在一趟排序的过程中没有进行交换操作或者是只有前两个数据发生了交换",这表明数据有序,可以结束排序。具体改进策略:记录每趟排序最后一次交换的位置lastindex(排序前先赋值为0),如果该趟排序结束之后,lastindex<=1,表示数据已经有序,可以结束排序;否则进行下一趟排序,排序范围:1~lastindex。
4、快速排序
基本思想:通过一趟排序将待排数据分成两部分,其中一部分的数据比另一部分小,然后对这两部分进行相同处理,使得整个序列有序;
递归:
以数组a为例,假设a含有n个元素。先设两个变量low和high,初始值
low=1,high=n;
然后,调用枢轴函数,寻找枢轴(一个数据)位置pos:
如果low = = == ==high,直接返回low;
否则,任意选一个元素a[i]作为枢轴,交换a[i]与a[low],然后将a[low]的值暂存到a[0];
实际选取枢轴的时候,为了避免pos = = == ==low或者pos = = == ==high,同时想让a[pos]把序列分为长度差不多的两部分,通常采用三值取中法:
首先,要确保至少有三个数据,即high-low>=2;
如果数据数目小于三个,直接排序就好了,没有必要再选取枢轴;
mid=(low+high)/2,a[mid],a[low],a[high]进行升序排序,
设置哨兵a[0]=a[low] , high=high-1;
循环1:如果low<high && a[high]>=a[0],则high=high-1,直到low = = == ==high或者a[high]<a[0],循环结束之后,如果low<high && a[high]<a[0],将a[high]赋值给a[low];
循环2:如果low<high && a[low]<=a[0],则low++,直到low==high或者a[low]>a[0],循环结束之后,如果low<high && a[low]>a[0],将a[low]赋值给a[high];
从循环1开始,依次进行两个循环,直到low = = == ==high,回代枢轴值:a[low]=a[0],并返回枢轴位置low;
然后,对a[low, ⋯ \cdots ⋯,pos-1]和a[pos+1, ⋯ \cdots ⋯,high]进行快速排序,如此进行下去,直到整个序列有序;
非递归(利用栈实现):
如果数组中元素个数不超过2,就地排序;
如果数组中元素个数大于等于3,将对应的low和high入栈;
如果栈非空,读取栈顶元素,即当前的low和high,弹栈;利用三值取中法得到枢轴位置pos,将序列分成两部分:
如果两个序列长度都不超过2,就地处理;
如果有一个超过2,另一个不超过2,就地处理长度不超过2的序列,将长
度大于2的序列的low和high入栈;
如果两个序列长度均大于2,选取较长的部分先入栈,为的是先对长度较
短的子序列进行排序,将栈的深度控制在 O ( log 2 N ) O(\log_2N) O(log2N) 。
然后继续弹栈,直到栈为空;
5、直接插入
假设要排序的数组为a,含有n个元素,第i趟直接插入排序的操作为:
在a[1, ⋯ \cdots ⋯,i-1]有序的情况下插入a[i],使得a[1, ⋯ \cdots ⋯,i]为有序序列;
如果a[i]>=a[i-1],直接i++;
若a[i]<a[i-1],设置哨兵a[0]=a[i],寻找插入位置,从a[i-2]开始向前查找,查找的同时将数据后移,直到找到比a[0]不大的元素a[j],然后将a[0]放到a[j+1],插入完毕;
接着进行第i+1趟插入,直到序列变成升序序列;
6、折半插入
直接插入在寻找插入位置时使用的是效率较低的顺序查找,为了充分利用前方序列的有序性,采用折半查找(或叫二分法查找)的方式来降低查找的时间复杂度。查找过程中的参数为:low,high,mid=(low+high)/2,如果 low<=high或者没有找到a[i],查找就一直进行。如果找到a[i],在mid+1位置插入;若查找失败,在low位置插入。
7、希尔排序
假设要排序的数组为a,含有n个元素,基本思想是:
先将a分割成若干“下标等差”的子序列,例如{a[1],a[3],a[5], ⋯ \cdots ⋯}和
{a[2],a[4],a[6], ⋯ \cdots ⋯},对每个子序列进行直接插入排序,使得数组a “基本有序”,最后对数组a进行一次直接插入排序。
这里采用的“增量”序列为 { 2 i − 1 } , i = k , k − 1 , ⋯   , 2 , 1 \lbrace2^i -1\rbrace,i=k,k-1,\cdots,2,1 {
2i−1},i=k,k−1,⋯,2,1
其中 k = ⌊ log 2 n ⌋ k= \lfloor \log_2 n\rfloor k=⌊log2n⌋ 。
8、简单选择排序
假设要排序的数组为a,含有n个元素,基本思想是:先选出a[1,…,n]中的最大值a[i],如果i!=n,将a[i]与a[n]进行交换,然后选出a[1,…,n-1]中的最大值a[i],如果i!=n-1,将a[i]与a[n-1]进行交换,依次进行下去,直到选出n-1个最大值为止。
9、堆排序
假设要排序的数组为a,含有n个元素,基本思想是:
将其看作是利用顺序表表示的完全二叉树,首先对所有的父节点进行调整,使树变为大顶堆;其次,进行n-1趟堆排序(以第i趟为例):
首先交换a[1]与a[n-i+1],然后,a的有效长度变为n-i,只需对a的根节点进行调整,使其重新变为大顶堆;
调整的具体做法为:
假设调整节点的编号为index,设置两个计数器i=index,j=2i,哨兵a[0]=a[i];
循环条件:j<=当前的数组长度:
如果j+1当前的数组长度且a[j+1]>a[j],j=j+1,保证j始终指向较大的孩子;
如果a[j]>a[0],则a[i]=a[j],更新i和j,i=j,j=2i;
如果a[j]<=a[0],跳出循环;
循环结束后,解除哨兵,a[i]=a[0],调整结束;
二、程序实现
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#pragma warning(disable:4996)
typedef struct node
{
int data;
struct node *next;
}LI;
//对数组和链表的处理函数
void getdata(int *a,int n);//获取n个数据存放到数组a中
int *copy(int *b,int n);//复制含有n个数据的数组b,返回复制得到的数组
void showarray(int *a,int n);//显示数组a,a有n个数据
LI *getlist(int n);//创建含有n个数据的无序链表
void showlist(LI *head);//显示链表数据,参数为头结点
void dellist(LI *head);//销毁链表,参数为头结点
//各种排序方法:
void radixsort(LI *head);//基数排序,参数为链表头结点
void mergesort(int *b,int n);//非递归归并排序,对含有n个数据的数组b进行排序
void Merge(int *a,int *t,int low, int mid, int high,int *comp,int *move);//归并两个有序数组a[low,,,mid]和a[mid+1,,,high],t用来暂存有序数组
void Mergesort(int *b, int n);//归并排序主调函数,对含有n个数据的数组b进行排序
void Msort(int *a,int *t,int low,int high,int *comp,int *move);//归并排序递归函数,对a[low,,,high]进行归并排序,其余参数说明见Merge函数
//交换类排序
void swap(int *a,int *b);//交换a,b指向的两个数据
void bubble(int *b,int n);//冒泡排序,对含有n个数据的数组b进行排序
void quicksort(int *b,int n);//非递归快速排序,对含有n个数据的数组b进行排序
int pivot(int *a, int low, int high, int *comp, int *move);//采用三数取中法在a[low,,,high]中选取枢轴
void Quicksort(int *b, int n);//快速排序主调函数,对含有n个数据的数组b进行排序
void Qsort(int *a,int low,int high,int *comp,int *move);//递归快速排序,对a[low,,,high]进行快速排序
//插入类排序
void straightins(int *b, int n);//直接插入排序,对含有n个数据的数组b进行排序
void binsertsort(int *b, int n);//折半插入排序,对含有n个数据的数组b进行排序
void shellsort(int *b,int n);//希尔排序,对含有n个数据的数组b进行排序
void shellins(int *a, int n, int t, int *comp, int *move);//间隔为t的插入排序,a为要排序的数组,含有n个数据
//选择类排序
void choosesort(int *b,int n);//对含有n个数据的数组b进行简单选择排序
void heapsort(int *b,int n);//对含有n个数据的数组b进行堆排序
void adjust(int index,int *a,int n,int *comp,int *move);//对利用顺序表a表示的含有n个节点的完全二叉树的index号节点进行筛选
int main(void)
{
int *a,n;
LI *head=NULL;
printf("请输入数据规模:\n");
scanf("%d",&n);
while(n>0)
{
head=getlist(n);
a=(int *)malloc((n+1)*sizeof(int));
getdata(a,n);
//若想查看排序结果,在相应的排序主函数中解除注释限制即可
radixsort(head);//基数排序
mergesort(a,n);//非递归归并排序
Mergesort