排序算法

排序是指将一列无序的元素,排列成一列递增或递减的有序序列。

  • 内部排序:指待排序记录全部存放在内存中进行排序的过程。
  • 外部排序:指待排序记录的数量很大,以至内存不能容纳全部记录,在排序过程中尚需对外存进行访问的排序过程。

常见的排序算法:

  1. 直接插入排序
  2. 冒泡排序
  3. 简单选择排序
  4. 希尔排序
  5. 快速排序
  6. 堆排序
  7. 归并排序

若在一个待排序序列中,元素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. 冒泡排序

方法:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

对于一组5个数{8,7,2,9,5}进行排序,用图像来形象解释冒泡排序为:

87777
78222
22888
99995
55559

这是第一趟排序,将最大的那个数排到了最后,进行了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)

例:

初始序列81941196123517952858417515
增量为5811942113964125351172953284585411752153
增量为5结果35171128124175159658819495

注:上标相同的为一个子序列,每一个增量完成,是指对这些分割的子序列进行直接插入排序完成。 接下来减小增量,继续分割后排序。

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}

满足堆定义的序列可直观地表示为一个完全二叉树。例如:
Alt
堆排序的基本思想是:对一组待排序元素,首先把它们按堆的定义排成一个序列(即建立初始堆),从而输出对顶的最小元素(最于小顶堆而言)。然后将剩余的元素再调整成新堆,便得到此次小的元素,如此反复,直到全部元素排成有序序列为止。


一组有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;
}
归并排序的时间复杂度为O(nlog2n),空间复杂度为O(n)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值