数组排序算法整理(不定期更新)

数组排序算法可以从代码形式上分为五大类:交换排序、选择排序、插入排序、归并排序、和基数排序,其中每一类又可以分为一些不同的小类(这就要靠我们自己总结);而从物理存储上,数组排序说到底是对CPU的合理调度——只分为内部排序、外部排序两类——其中不借助磁盘IO,所有的操作都在内存中完成的叫内部排序,而对于那些不便于一次性读入的数据进行排序,就要用到外部排序的技巧;通常不做说明默认就是内部排序


**目录**

文章目录

  • 交换排序(Swap Sort)
    • 冒泡排序(Bubble Sort)
    • 快速排序(Quick Sort)
  • 选择排序(Selection Sort)
    • 直接选择排序(Straight Select Sort)
    • 树状选择排序(Tree Select Sort)
    • 堆排序(Heap Sort)
  • 插入排序(Insertion Sort)
    • 直接插入排序(Straight Insert Sort)
    • 二分插入排序(Binary Insert Sort)
    • 希尔排序(Shell Sort)
  • 归并排序(Merge Sort)


在正式开始前,先看图有一个整体的印象

数组排序算法整理(不定期更新)_第1张图片


交换排序(Swap Sort)

说起交换排序,条件反射的会立即想到swap函数——交换排序需要swap,并且总是发生在两两一组之间;交换排序大致分为冒泡和快排

冒泡排序(Bubble Sort)

这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端,所以叫『冒泡』

它重复地走访过要排序的数列一次比较两个元素,从开始第一对到结尾的最后一对(一共n-1趟比较,每趟比较又分为若干比较),如果顺序错误就把他们交换过来;在这一点,每趟比较后靠最后的元素是最大的数——所以每次都可以减少一些元素不用参与比较

相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法

冒泡排序的优点是简单(也是我接触的第一个排序算法),缺点是要进行大量的比较和交换

冒泡排序的时间复杂度是O(N^2),在最好情况下,可以达到O(N-1)

如果非要细究的话,冒泡排序也有两种写法(两个名字是我瞎编的)

[cpp]
/*函数功能:实现冒泡排序(写法一,冒泡法)
 *请参说明:a[]:待排序数组,n:数组中的元素个数
 */
void bubblesort(int a[], int n)
{
	int i, j, tmp;
	for(i=0; i a[j+1])
			{
				tmp = a[j];
				a[j] = a[j+1];
				a[j+1] = tmp;
			}
		}
	}
}
[cpp]
/*函数功能:实现冒泡排序(写法二,也叫沉底法)
 *请参说明:a[]:待排序数组,n:数组中的元素个数
 */
 void bubblesort(int a[], int n)
 {
	 int i, j, tmp;
	 for(i=0; i=0; j--)
		 {
			 if(a[j] > a[j+1])
			 {
				 tmp = a[j];
				 a[j] = a[j+1];
				 a[j+1] = tmp;
			 }
		  }
	
	}
}

快速排序(Quick Sort)

快排是已知最快的数组排序算法(务必熟练掌握)

由C. A. R. Hoare在1962年提出,不妨看成是冒泡排序的改进算法

它的思路是,通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列——眼尖的同学肯定看出来快排可以使用递归实现

[cpp]
/*函数功能:实现快速排序
 *请参说明:a[]:待排序数组,low:组的左边界,high:组的右边界
void quicksort(int a[], int low, int high)
{
	if(low >= high) return;//如果左边索引大于或等于右边索引,就代表已经完成一个分组
	int i = low, j = high;
	int key = a[low];//去小端作为每次判断的关键值,即中间数

	while(i < j)//在当前[i..j]组内找一遍
	{
		while(key <= a[j])//寻找结束的条件是,1)找到一个大于key的值,2)没有找到符合1条件的,并且i和j的大小没有交换
		{
			j--;//不断向前寻找
		}
		a[i] = a[j];//找到一个这样的数就把它赋值给前面被拿走的i的值
		while(key >= a[i])//i在当前组内向前寻找,不过key的大小关系通知循环和上面的完全相反,因为排序思想是把数往两边扔,所以左右两边的数的大小与key的关系相反
		{
			i++;
		}
		a[j] = a[i];
	}
	a[i] = key;//在当前组内找完一遍后就以把中间数key回归
	quicksort(a, low, i-1);//用同样的方式对分出来的左边的小组进行上述操作
	quicksort(a, i+1, high);//用同样的方式对分出来的右边的小组进行上述操作
	/*直到每一组的i==j为止*/
}

选择排序(Selection Sort)

有三种不同的选择排序:分别是:直接选择排序(Straight Select Sort)、树状选择排序(锦标赛排序,Tournament Sort)、堆排序(Heap Sort)

直接选择排序(Straight Select Sort)

也叫简单选择排序(Simple Select Sort),就是不管是写起来还是理解起来都十分简单的排序算法

和Bubble Sort一样,它也要n-1趟遍历;第i一趟遍历从A[i-1]~A[n-1]中选出最小的元素,并把最小的元素放到排头,这样在n-1趟后数组就有序了

直接选择排序的时间复杂度是O(N^2)

由于在直接选择排序中存在着不相邻元素之间的互换,因此直接选择排序是一种不稳定的排序方法

[cpp]
/*函数功能:实现直接选择排序
 *请参说明:a[]:待排序数组,n:数组中的元素个数
 */
void simpleselectsort(int a[], int n)
{
	int i,j,min,tmp;
	for(i=0; i

树状选择排序(Tree Select Sort)

树状选择排序又称锦标赛排序(Tournament Sort),是一种按照锦标赛的思想进行选择排序的方法

首先对n个记录的关键字进行两两比较,然后在n/2(向上取整)个较小者之间再进行两两比较,如此重复,直至选出最小的记录为止

树形选择排序构成的树是满二叉树

其中,树叶结点包括所有的参赛者,两两比较,数值小的上升成为父亲结点,每一趟排序的产生一个新的树根

数组排序算法整理(不定期更新)_第2张图片

在下一趟遍历开始前,把这一趟的优胜者之兄弟结点上升到父亲结点的位置

数组排序算法整理(不定期更新)_第3张图片

树状选择排序的时间复杂度是O(N*logN)

树状选择排序的缺点是辅助存储空间较多,并且需要和最大值进行多余的比较(于是堆排序应运而生,详见后文)

[cpp]
/*函数功能:实现树状选择排序
 *请参说明:a[]:待排序数组,n:数组中的元素个数
 */
void treeselectsort(int a[], int n)
{
	/*相比树状排序我更推荐堆排序所以请先忽略树状排序,这里先占着以后补上
	 *总体的思路很简单,就是自底向上构造一棵满二叉树——不过涉及到的细节还是容易出错
	 */
}

堆排序(Heap Sort)

为了弥补树状选择排序的不足,堆排序应运而生(同样属于选择排序)

1991年的计算机先驱奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德(Robert W.Floyd)和威廉姆斯(J.Williams)在1964年共同发明了著名的堆排序算法

堆分为大根堆和小根堆,是完全二叉树

大根堆要求每个结点的值都不大于其父亲结点的值,小根堆要求每个结点的值都不小于其父亲结点的值(也就是说,大根堆=>树根最大值,小根堆=>树根最小值)

堆排序的时间复杂度是O(N*logN)

易知,大根堆=>升序,小根堆=>降序

下面以大根堆讲解升序排序

[cpp]
void heapsort(int a[], int n)
{
	int i,j,root_index;
	int length = sizeof(a)/sizeof(int);
	for(i=length; i>=0; i--)
	{
		//从最后一个元素开始想堆的上层查找
		for(j=i-1; j>0; j--)
		{
			if(j%2==0)//右子结点的根结点
				root_index = (j-2)/2;
			else//左子结点的根结点
				root_index = (j-1)/2;
				
			if(a[root_index] < a[j])//如果根结点比当前结点小,不满足堆的性质,交换二者
			swap(&a[root_index], &a[j]);
		}
		swap(&a[j], &a[i-1]);//将最大值向后移动
	}	
}		

插入排序(Insertion Sort)

插入排序分为直接插入排序、二分插入排序和希尔排序,如果不做说明默认就是直接插入排序

直接插入排序(Straight Insert Sort)

看到名字就想到了『直接选择排序』,二者的共同点是它们的复杂度都是O(N^2),但是直接插入排序相比直接选择排序更稳定(所谓稳定,是指原来相等的两个数不会交换位置)

直接插入排序在最好情况下(所有元素都基本有序),时间复杂度为O(N)

插入排序的基本思想是:每步将一个待排序的记录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止

第一趟:对下标1的元素排序,保证[0…1]上的元素有序
第二趟:对下标2的元素排序,保证[0…2]上的元素有序
……
第n-1趟:对下标n-1的元素排序,保证[0…n-1]的元素有序

[cpp]
/*函数功能:实现直接选择排序(方案一)
 *请参说明:a[]:待排序数组,n:数组中的元素个数
 */
void simpleinsertsort(int a[], int n)
{
	int i,end,tmp;
	for(i=1; i=0 && arr[end] >tmp)
		{
			arr[end+1] = arr[end];
			--end;
		}
		arr[end+1] = tmp;
	}
}
[cpp]
/*函数说明:直接插入排序的优化版
 *请参说明:a[]:待排序数组,n:数组中的元素个数
 */
 void simpleinsertsort(int a[], int n)
 {
	 int i,end, tmp;
	 for(i=1; i=0; end--)
		 {
			 if(a[end] > a[end+1])//end+1就是i,发现前一个数比当前的数还大
			 swap(&arr[end], &arr[end+1]);//交换之
			 else//否则说明i之前的序列都已经有序了
				 break;
		}
	}
}

二分插入排序(Binary Insert Sort)

也叫折半插入排序,基于直接插入,把寻找a[i]位置的方法改为折半比较

所谓折半,就是指插入a[i]时,取a[(low+high)/2]的值和a[i]比较,而不是(a[i-1]和a[i]进行比较)

二分插入排序算法在最好的情况下的时间复杂度为O(N*logN),最坏情况和知己插入一样,是O(N^2)

[cpp]
/*函数功能:实现二分插入排序
 *请参说明:a[]:待排序数组,n:数组中元素的个数
 */
void binaryinsertsort(int a[], int n)
{
	int i,j,low,high,mid,tmp;
	for(i = 1; i tmp)
			{
				high = mid - 1;
			}
			else
			{
				low = mid + 1;
			}
		}
		for(j=i-1; j>=low; j--)
		{
			a[j+1] = a[j];
		}
		a[low] = tmp;
	}
}

希尔排序(Shell Sort)

希尔排序也叫『缩小增量排序』,由DL.Shell于1959年提出而得名

希尔排序可以看成是分组的直接插入排序

希尔排序的时间复杂度为O(N^1.3)

其基本思想是:先将整个待排序数组分割成若干个子序列(由相隔某个增量的元素组成)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序,因为直接插入排序在元素基本有序的情况下效率时很高的,所以希尔排序在时间上较之前的插入排序有较大提升

[cpp]
/*函数功能:希尔插入,封装待希尔排序中
 *请参说明:a[]:待排序数组,n:数组中的元素个数,dk:当前增量
 */
void shellinsert(int a[], int n, int dk)
{
	int i,j,tmp;
	for(i=dk; ii%dk && a[j] > tmp; j-=dk)
		{
			a[j+dk] = a[j];
		}
		if(j != i-dk)
		{
			a[j+dk] = tmp;
		}
	}
}

/*函数功能:计算Hibbard增量
 *请参说明:t、k:就是一个值
 */
int dkHibber(int t, int k)
{
	return (int)(pow(2, t-k+1)-1);
}

/*函数功能:实现希尔排序(插入排序的重组改进版本)
 *请参说明:a[]:待排序数组,n:数组中元素的个数,t:就是t
 */
void shellsort(int a[], int n, int t)
{
	shellinsert(a, n, dk);
	int i;
	for(i=1; i

归并排序(Merge Sort)

发明者是大名鼎鼎的John von Neumann,现代计算机之父

从名字可以看出来这是一种先局部后整体的排序方式(类似的如quicksort,体现了分而治之,Divide and Conquer的算法思想),即『先划分,后合并』;可想而知,这要用到递归实现

归并排序是相当稳定的,不想别的算法存在最好和最坏情况,它的时间复杂度稳定为O(N*logN)

它的核心操作就是『归并』,即Merge操作

归并 = 递归 + 合并

归并排序不是原地排序,在排序过程中要申请新的内存空间用来存放临时数组元素,相对应的我们说归并排序(包括后面讲的基数排序)都是复制排序

[cpp]
/*函数功能:合并a[first..mid]和a[mid..last],合并结果存放到tmp中去
 *请参说明:a[]:待排序数组,first:合并区间的首个下标,last:合并区间的最后一个下标,tmp[]:用来存放合并结果的临时数组
 */
 void mergearray(int a[], int first, int last, int last, int emp[])
 {
	int i = first, j = mid + 1;
	int m = mid, n = last;
	in k = 0;
	 
	while(i <= m && j <= n)
	{
		if(a[i] <= a[j]) tmp[k++] = a[i++];
		else tmp[k++] = a[j++];
	}
	
	while(i <= m)
		tmp[K++] = a[i++];
	while(j <= n)
		tmp[k++] = a[j++];
	for(i=0; i

你可能感兴趣的:(算法和数据结构,算法,数组排序)