目录
简单排序
冒泡排序
每次比较相邻的两个元素,把较大(小)者换到后面,每一趟能使一个在无序序列中最大(小)的元素确定自己的位置。执行n趟即可使整个数组有序,时间复杂度最好为O(N),平均、最坏都为O(N^2),空间复杂度O(1),是稳定的。
void BubbleSort(Item a[],int n)
{
for(int i=0;i<n-1;i++) //执行n趟
for(int j=0;j<n-1-i;j++) //第i趟的a[n-i]~a[n]已经排好序,不用重复比较
{
if(a[j]>a[j+1]) //以升序排列为例
{
swap(a[j],a[j+1]);
}
}
}
插入排序
每次把无序序列的第一个元素来与有序序列中的元素逐个比较,插在合适的位置,形成新的有序序列,执行n次,直到整个序列都是有序的。这种方法能保证子前序列都是排好序的。时间复杂度最好为O(N),平均、最坏为O(N^2),空间复杂度为O(1),是稳定的。
void InsertSort(Item a[],int n)
{
for(int i=1;i<n;i++) //执行n趟,每趟i都指向无序序列的第一个元素
{
int j=i-1; //初始化j,指向有序序列的最后一个元素
while(j>1&&a[j]>a[i]) //当待插入元素小于当前j指向的元素,则将a[j]往后移一位,为待插入元素腾位置
{
a[j]=a[j-1]; //再继续检查前面的所有元素,直到a[j]<a[i],此时j指向的就是需要插入的位置
j--;
}
a[j]=a[i];
}
}
选择排序
每次从无序序列中找出最值,然后与无序序列的第一个元素交换位置,这种方法能保证子前序列都是排好序的。时间复杂度最好、最坏、最差都为O(N^2),因为无论是否有序都要一个个探测最小值。空间复杂度为O(1),是不稳定的,例如:[5 8 5 2 9],第一个5与2交换后,就到了第二个5后面了,它们的相对位置改变了。
void SelectSort(Item a[],int n)
{
for(int i=0;i<n;i++)
{
for(int j=i,min_pos=i;j<n;j++) //a[i]~a[n]是无序区间,初始化最小元素下标为i
{
if(a[j]<a[min_pos]) //更新最小元素下标
{
min_pos=j;
}
}
swap(a[i],a[min_pos]) //交换无序区间第一个元素与无序区间最小元素的位置
}
}
快速排序
以数组中某个元素为枢轴(划分基准),每趟递归调用都使枢轴左边都是小于枢轴的值,右边都是大于枢轴的值,因此每次递归调用都能确定一个元素的最终位置。递归结束的条件是L>=R,即待排序的序列中只有一个元素时,它显然有序,则结束当前递归返回上一层递归。
void QuickSort(Item a[],int l,int r)
{
if(l>=r) return; //序列中只有一个元素时结束递归
int i=partition(a,l,r); //获得枢轴的下标
QuickSort(a,l,i-1); //对枢轴左半部分调用快排
QuickSort(a,i+1,r); //对枢轴右半部分调用快排
}
int partition(Item a[],int l,int r)
{
//规定一个元素为枢轴,并获得它的最终位置,使在它左边的数都小于它,在它右边的数都大于它
//让元素a[l]的值作为枢轴值
int x=a[l];
int i=l,j=r;
while(i!=j)
{
while(j>i&&a[j]>=x) j--;
if(j>i) //此时退出while是因为j指向比枢轴值小的数
{
a[i]=a[j]; //直接覆盖i指向的元素,放在枢轴左边
i++;
}
while(j<i&&a[i]<x) i++;
if(j>i) //此时i指向比枢轴值大的数
{
a[j]=a[i]; //放在枢轴右边
j--;
}
//由于第一次覆盖的元素就是枢轴,是我们已知的,所以直接覆盖不会丢失数据。
//而在这之后被覆盖的元素都是无关紧要的,因为它的值在上一步已经去了它该去的位置了。
//执行到最后,只有枢轴值x不知道它该去的位置在哪里,显然,就是i=j的位置。
}
a[i]=x;
}
合并排序
将待排序列分成大小大致相等的左右两端,接着依次分别对这两端子序列递归地进行合并排序,直到序列中只有一个元素时,结束递归。每一轮递归结束时,某区域左右两边都分别有序。此时,再将排好的已经有序的两个子序列合并为一个序列,放在一个工作区,不断返回上一层递归,直到合并成原来长度的序列。最后用工作区排好序的全序列去更新原来待排序的元素序列。
void MergeSort(Item a[],int l,int r)
{
int mid=(l+r)/2;
if(r<=l) return;
MergeSort(a,l,mid);
MergeSort(a,mid+1,r);
MergeAB(a,b,l,m,r);
}
void MergeAB(Item a[],Item b[],int l,int m,int r)
{
int i=l,j=mid+1,k=0; //指针指向两个子序列的第一个元素
while(i<=mid&&j<=r) //若未超出所指示的子序列,则将较小值存入工作区b
{
if(a[i]<a[j]) b[k++]=a[i];
else b[k++]=a[j];
}
if(i>m) //若i先超出所子序列,则只需把j还未遍历的数组全部赋给工作区b
{
while(j<=r) b[k++]=a[j++];
}
else //若i先超出所子序列,则只需把j还未遍历的数组全部赋给工作区b
while(i<=mid) b[k++]=a[i++];
for(int i=l;i<=r;i++)
{
a[i]=b[i]; //再把工作区b里的序列复制到数组a去
}
}
堆排序
把待排序的序列按完全二叉树的结构特点存入数组,关键字存储必须从1开始,数组元素i的左孩子为2i,右孩子为2i+1。建好堆之后,要进行排序,对一个有n个元素的序列进行排序,相当于执行n次堆的调整,输出当前最小(大)的元素之后,用数组中最后一个元素(即完全二叉树的最后一个叶子结点)取代堆顶元素,引起堆的再次调整。
void Sift(Item R[],int low,int high)
{
//这是一个自上而下的堆调整
int i=low,j=2*i; //j是i的左孩子
int temp=R[i];
while(j<=high)
{
if(j<high&&R[j]<R[j+1]) //右孩子更大
{
j++; //j指向右孩子
}
/*以上操作使j指向左右孩子中较大的一个*/
if(temp<R[j])
{
R[i]=R[j];
i=j; //i下降,不用管另一个孩子,因为R[i]与R[j]的交换没有影响到另一个孩子及其子树
j=i*2; //j仍指向i的左孩子
}
else break;
}
R[i]=temp; //被调整结点的值放入最终位置
//最终要找到一个R[j]不大于当前根节点R[low]的地方将temp放进去,如果j走到最后都没有这样的位置,说明R[low]就是最大的,就把一开始放在temp里的值再还给R[low]
}
/*堆排序函数*/
void HeapSort(Item R[],int n)
{
for(int i=n/2;i>=1;i--) //从最后一个叶子结点的父节点开始,自下而上,自右向左调整,每次以结点i为堆的根结点
Sift(R,i,n);
/*上面是堆的初始化,下面是每一轮最值的输出和后续的调整*/
for(int i=n;i>=2;i--) //从最后一个叶子结点开始,自下而上,自右向左,每次用最后一个叶子结点替换堆顶结点,从而引起新一轮的调整
{
temp=R[1];
R[1]=R[i];
R[i]=temp;
Sift(R,1,i-1); //开始新的一轮调整是从下到下,从左到右的,由于第i个元素已经有序,所以只需要调整到第i-1个元素
}
}
也就是说,堆排序的排序过程就是从最后一个叶子结点的父节点开始,从右到左,从下到上地,对以该节点为根的树再进行从上到下,从左到右的调整,直到整个完全二叉树中的每个子树都满足大(小)根堆。输出堆顶后,用最后一个叶子结点替换堆顶结点,此时的堆可能不满足大(小)根堆的性质,需要重新进行调整,此时只需要自堆顶从上往下,从左往右调整即可,
计数排序
找出序列的最大值max和最小值min,以它们的差dist作为长度开辟一个数组空间c,遍历序列a,每次访问到一个a[i],就执行c[min+i]++,最后按顺序将c[min+i]个a[i]放到一个长度为length(a)的数组中去。
桶排序
比如对2位数进行排序,先根据基数申请10个桶,再按十位的取值将元素放进相应的桶中,然后对每个桶的内部进行排序,将排好序的桶内元素拼接起来就得到有序序列。
基数排序
其实思路跟桶排序差不多,但只适用于整数排序 ,先以个位为基准进行排序,排序结果拼接起来成为一个假有序序列,再对这个序列以十位为基准进行排序,排序结果拼接起来得到一个真正有序的序列。
图片转载自:https://www.jianshu.com/p/134a0eed5e3a