排序是指将一列无序的元素,排列成一列递增或递减的有序序列。
- 内部排序:指待排序记录全部存放在内存中进行排序的过程。
- 外部排序:指待排序记录的数量很大,以至内存不能容纳全部记录,在排序过程中尚需对外存进行访问的排序过程。
常见的排序算法:
- 直接插入排序
- 冒泡排序
- 简单选择排序
- 希尔排序
- 快速排序
- 堆排序
- 归并排序
若在一个待排序序列中,元素a[i]和a[j]的值相同,且在排序之前a[i]领先于a[j],那么在排序后,如果a[i]和a[j]的相对位置不变,a[i]仍领先于a[j],则称此类排序为稳定
的。若在排序后,可能会出现a[j]领先于a[i]的情况,则为不稳定
的。
其中,直接插入排序、冒泡排序、归并排序是稳定排序
;简单选择排序(直接选择排序)、希尔排序、快速排序、堆排序是不稳定排序
。
1. 直接插入排序
类似于整理扑克牌,每一步将一个待排序的元素,按其大小插入到前面已经排好序的一组元素中去。
C代码:
void insertSort(int data[],int n)
//对有n个元素的数组date进行非递减插入排序
{
int i,j,t;//t为一个辅助空间
for(i=1;i<n;i++)//默认date[0]元素已经排好,从第二个元素date[1]开始选取未排元素插入到前面排好的序列里
{
t=data[i];
j=i-1;//date[j]为待排元素的前一个,即已排好元素的最后一个
while(j>=0 && data[j]>t)
{//已排好元素从最后一个往前依次与待排元素比较,如果大于待排元素则往后移
data[j+1]=data[j];
j--;
}
data[j+1]=t;
}
}
直接插入排序的时间复杂度为Ο(n2),由于仅需一个元素的辅助空间,所以空间复杂度为Ο(1)。
2. 冒泡排序
方法:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
对于一组5个数{8,7,2,9,5}进行排序,用图像来形象解释冒泡排序为:
8 | 7 | 7 | 7 | 7 |
---|---|---|---|---|
7 | 8 | 2 | 2 | 2 |
2 | 2 | 8 | 8 | 8 |
9 | 9 | 9 | 9 | 5 |
5 | 5 | 5 | 5 | 9 |
这是第一趟排序,将最大的那个数排到了最后,进行了4次比较。然后进行第二趟排序,第二大的数将排到倒数第二的位置,进行了3次比较。所以,对于一个有n个元素的序列,需要进行n-1趟的冒泡排序;第一趟要比较n-1次,第二趟要比较n-2次,依次类推……
C代码:
void bubbleSort(int data[],int n)
{//对有n个元素的数组date进行非递减冒泡排序
int i,j,t;
for(i=1;i<n;i++)//外循环控制趟数
{
for(j=0;j<n-i;j++)//内循环控制每趟的比较次数
{
if(data[j]>data[j+1])
{
t=data[j];
data[j]=data[j+1];
data[j+1]=t;
}
}
}
}
冒泡排序时间复杂度为Ο(n2),由于仅需一个元素的辅助空间,所以空间复杂度为Ο(1)。
3. 简单选择排序
原理:
第一轮,选择序列的第一小元素与序列位置第一个元素交换
第二轮,选择序列的第二小元素与序列位置第二个元素交换
……
C代码:
void selectSort(int data[],int n)
{//对有n个元素的数组date进行非递减选择排序
int i,j,min,t;
for(i=0;i<n-1;i++)
{
min=i;//min用来存储每一轮选择的最小值的下标,初始默认为每一轮的第一个
for(j=i+1;j<n;j++)
{
if(data[j]<data[min])
min=j;
}
if(min!=i)
{
t=data[i];
data[i]=data[min];
data[min]=t;
}
}
}
简单选择排序时间复杂度为Ο(n2),由于仅需一个元素的辅助空间,所以空间复杂度为Ο(1)。
4. 希尔排序
希尔排序又称“缩小增量排序”,是对直接超如排序的改进。
由于直接插入排序在“序列基本有序”;“元素个数较少”时的效率较高。因此,希尔排序的基本思想是:先将整个待排子元素序列分割成若干子序列,然后分别进行直接插入排序,待整个序列的元素基本有序时,再对全体元素进行一次直接插入排序。
具体做法:设置一个增量序列d[m] (d[0]>d[1]>…>d[m],且d[m]=1),增量序列应该是互质的。对于待排序列{a1,a2,a3,a4,a5,a6,a7,a8,a9,a10},假如增量d[0]=5,则分成的子序列为{a1,a6},{a2,a7},{a3,a8},{a4,a9},{a5,a10},对这些子序列进行插入排序,排好后再以d[1]为增量进行分割子序列进行排序,依次类推,最后再进行一次直接插入排序(增量为1)
例:
初始序列 | 81 | 94 | 11 | 96 | 12 | 35 | 17 | 95 | 28 | 58 | 41 | 75 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
增量为5 | 811 | 942 | 113 | 964 | 125 | 351 | 172 | 953 | 284 | 585 | 411 | 752 | 153 |
增量为5结果 | 35 | 17 | 11 | 28 | 12 | 41 | 75 | 15 | 96 | 58 | 81 | 94 | 95 |
注:上标相同的为一个子序列,每一个增量完成,是指对这些分割的子序列进行直接插入排序完成。 接下来减小增量,继续分割后排序。
C代码:
#include <stdio.h>
void insertSort(int data[],int n,int d)
{
int i,j,k,t;
for(k=0;k<n;k++)
{
for(i=k+d;i<n;i+=d)
{
t=data[i];
j=i-d;
while(j>=0 && data[j]>t)
{
data[j+d]=data[j];
j=j-d;
}
data[j+d]=t;
}
}
}
void shellSort(int data[],int n,int d[],int m)
// 对data数组的n个元素进行非递减希尔排序
{// d数组为增量数组
int i;
for(i=0;i<m;i++)//在每个增量下进行插入排序
insertSort(data,n,d[i]);
}
int main()
{
int a[10]={74,34,54,3,23,41,65,77,43,51};
int d[3]={5,3,1};
shellSort(a,10,d,3);
int i;
for(i=0;i<10;i++)
printf("%d ",a[i]);
return 0;
}
希尔排序的时间复杂度不统一,O(n1.3—n2)),在程序员教程上看到是Ο(n1.3),空间复杂度为Ο(1)。
5. 快速排序
基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序。
C代码:
#include <stdio.h>
int Partition(int c[],int left,int right)
{//划分函数,返回将数组分成左右两段的那个元素的位置
//一般默认将数组第一个元素作为划分元素
int i=left,j=right+1,t;
do
{
do i++;while(c[i]<c[left]);//从左往右依次找大于数组第一个元素的元素,找到一个就暂停
do j--;while(c[j]>c[left]);// 从右往左依次找小于数组第一个元素的元素,找到一个就暂停
if(i<j) //如果i的位置还在j的前面,则交换c[i]和c[j]
{
t=c[i]; c[i]=c[j];c[j]=t;
}
}while(i<j); //如果i的位置还在j的前面,就一直找一直交换,直到i>j
/*将j位置的元素与数组第一个元素交换,交换后,就可以得到 j位置的前面元素都不大于c[j],
j后面的元素都不小于j*/
t=c[left];
c[left]=c[j];
c[j]=t;
return j;//返回将数组划分为前后两部分的元素位置j
}
void QuickSort(int a[],int low,int high)
{//采用递归的方式,再对左右两部分继续划分
int p;//p为中轴元素的位置
if(low<high)
{
p=Partition(a,low,high);
QuickSort(a,low,p-1);
QuickSort(a,p+1,high);
}
}
int main()
{
int b[9]={56,43,23,67,78,72,34,97,80};
int i;
QuickSort(b,1,8);
for(i=1;i<=8;i++)
printf("%4d",b[i]);
getch();
return 0;
快速排序的时间复杂度为O(nlog2n)。空间复杂度O(logn)~O(n)。
6. 堆排序
堆的定义:
对于n个元素的序列{k1,k2,……,kn},当且仅当满足下列关系时称其为堆。
{ki ≤ \leq ≤k2i && ki ≤ \leq ≤k2i+1} 或 {ki ≥ \geq ≥k2i && ki ≥ \geq ≥k2i+1}
满足堆定义的序列可直观地表示为一个完全二叉树。例如:
堆排序的基本思想是:对一组待排序元素,首先把它们按堆的定义排成一个序列(即建立初始堆),从而输出对顶的最小元素(最于小顶堆而言)。然后将剩余的元素再调整成新堆,便得到此次小的元素,如此反复,直到全部元素排成有序序列为止。
一组有n个元素的完全二叉树编号{0,1,2,……,n-1},对`i节点`有如下规律: * 父节点 parent=(i-1)/2 (向下取整) * 左孩子节点 child1=2i+1 * 右孩子节点 child2=2i+2
想要让一组有n+1个元素的序列排成的完全二叉树变成堆,需要从编号为(n-1)/2的结点开始与其左右孩子比较大小,如果想变化成大顶堆,则父节点小于孩子节点,就要交换节点的值。依次递减的比较并交换,即下一个比较编号为(n-1)/2-1的结点与其左右孩子节点的大小。
C代码:
#include <stdio.h>
void swap(int arr[],int i,int j)
{
int t=arr[i];
arr[i]=arr[j];
arr[j]=t;
}
void heapfiy(int tree[],int n,int i)
//对有n个元素的数组tree,对i结点比较
{
if(i>=n) return;//递归出口
int child1=2*i+1; //左孩子
int child2=2*i+2; //右孩子
int max=i;
if(child1<n && tree[child1]>tree[max])
{
max=child1;
}
if(child2<n && tree[child2]>tree[max])
{
max=child2;
}
if(max!=i) //将根节点与其左右孩子中最大的元素,交换到根节点
{
swap(tree,max,i);
}
heapfiy(tree,n,i+1);
//递归的对后面的结点做上面的操作,虽然递归结束还不能得到一个堆
}
void build_heap(int tree[],int n)
{//将n个元素的数组tree建立堆
int last_node=n-1;
int parent=(last_node-1)/2; //最后一个结点的父节点
for(parent;parent>=0;parent--)
{//从最后一个结点的父节点开始依次递减的进行heapfiy
heapfiy(tree,n,parent);
}
}
void heap_sort(int tree[],int n)
{
build_heap(tree,n);//先对n个元素排成堆
int i;
for(i=n-1;i>=0;i--)
{
swap(tree,i,0);//将堆顶元素与最后一个元素交换
build_heap(tree,i);//将剩下的n-1个元素排成堆
}
}
int main()
{
int tree[8]={34,98,23,44,29,23,49,67};
heap_sort(tree,8);
int i;
for(i=0;i<8;i++)
{
printf("%d ",tree[i]);
}
return 0;
}
堆排序的时间复杂度为O(nlog2n),空间复杂度为O(1).
7. 归并排序
“归并”
:是指将两个或两个以上的有序子序列合并成一个有序序列的过程。
归并操作的工作原理如下:
- 第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
- 第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
- 第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
- 重复步骤3直到某一指针超出序列尾
- 将另一序列剩下的所有元素直接复制到合并序列尾
C代码:
#include <stdio.h>
void Merge(int *c,int left,int mid,int right)
{//合并子序列
int d[right-left+1];
int i,j,k=0,cnt;
i=left;
j=mid+1;
while(i<=mid && j<=right)
{
if(c[i]<=c[j])
d[k++]=c[i++];
else
d[k++]=c[j++];
}
//第一路先结束,第二路后面的内容放到数组d的后面
while(j<=right)
d[k++]=c[j++];
//第二路先结束,第一路后面的内容放到数组d的后面
while(i<=mid)
d[k++]=c[i++];
for(cnt=0, i=left; i<=right; cnt++,i++)
c[i]=d[cnt];
}
//二路归并排序
void MergeSort(int a[],int left,int right)
{
int mid;
if (left<right)
{
mid=(left+right)/2;
MergeSort(a,left,mid);
MergeSort(a,mid+1,right);
Merge(a,left,mid,right);
}
}
int main()
{
int i,b[8]={43,56,67,78,72,34,97,80};
for(i=0;i<=7;i++)
printf("%4d",b[i]);
printf("\n");
MergeSort(b,0,7);
for(i=0;i<=7;i++)
printf("%4d",b[i]);
getch();
return 0;
}