排序算法的总结

说明:以下所有排序算法中nums数组都是从0开始,部分算法也给出了nums数组从1开始的情况。第i趟排序中的i也是从0开始。


直接插入排序:假设部分序列有序,然后将无序的部分循环插入到已有序的序列中。对于随即顺序的序列的时间复杂度:O(N^2),对于基本有序的序列时间复杂度:O(N),空间复杂度:O(1)。代码如下:

/*Straight Insertion Sort
**时间复杂度:O(n^2) 最好:O(n) 
**空间复杂度:O(n)
*/
void straight_insert_sort(int *nums, int n) {
	int i, j, temp;

	for (i = 1; i < n; i++) {
		j = i-1;
		temp = nums[i];
		while (j >= 0 && nums[j] > temp) {
			nums[j+1] = nums[j];
			j--;
		}
		nums[j+1] = temp;
	}
}
 

二分(折半)插入排序:假设部分序列有序,然后将无序的部分循环插入到已有序的序列中,与直接插入排序不同的地方在于每次比较的是有序序列的中间位置,这就有效的减少了比较的次数,但是每趟比较完需要移动的次数并不会减少,时间复杂度自然不变。对于随即顺序的序列的时间复杂度:O(N^2),有序序列时的时间复杂度大于O(n),空间复杂度:O(1)。代码如下:

/*Binary Insertion Sort
**时间复杂度:O(n^2) 最好:O(n) 
**空间复杂度:O(1)
*/
void binary_insert_sort(int *nums, int n) {
	int i, j, low, high, m, temp;
	for (i = 1; i < n; i++) {
		low = 0;
		high = i-1;
		temp = nums[i];
		while (low <= high) {
			m = (low + high)/2;
			if (nums[m] > temp) {
				high = m - 1;
			}
			else {
				low = m + 1;
			}
		}
		for (j = i-1; j > high; j--) {
			nums[j+1] = nums[j];
		}
		nums[high+1] = temp;
	}
}


希尔排序:将待排序列分割成若干子序列分别进行直接插入排序,第i趟的增量设置为dk[i],换而言之就是分成dk[i]个子序列。直接插入排序可以看做是增列恒定为1的希尔排序。时间复杂度:小于O(N^2),空间复杂度:O(1)。注意:增量序列中的值没有除1以为的公因子,并且最后一个增量必须为1,代码如下:

/*Shell Insert Sort
**dk数组存放每趟插入的增量值,最后一个增量为1,k趟插入
**最后一趟插入前已基本有序
**时间复杂度:O(n^2) 较staight insert sort 减少了移动次数
**空间复杂度:O(1)
*/
void shell_insert_sort(int *nums, int n, int *dk, int k) {
	int i, j, z, temp;
	for (i = 0; i < k; i++) { //k个增量值,k趟插入
		for (j = dk[i]; j < n; j++) { //第j个数
			z = j - dk[i];
			temp = nums[j];
			while (z >= 0 && nums[z] > temp) { //找到第一个小于待插入数的下标z
				nums[z+dk[i]] = nums[z];
				z -= dk[i];
			}
			nums[z+dk[i]] = temp; //将待插入数插入到z右边第dk[i]位置
		}
	}
}


冒泡排序:每一趟从序列第一个元素开始比较nums[i] 和nums[i+1],直到序列最后一个元素,总共进行n-1趟。对于随即顺序的序列共进行比较(n-1)+(n-2)+...+1次,平均下来交换次数为比较次数的一半。时间复杂度:O(N^2),空间复杂度:O(1)。代码如下:

/*Bubble Sort
**时间复杂度:O(n^2)
**空间复杂度:O(1)
*/
void bubble_sort(int *nums, int n) {
	int i, j, temp;
	for (i = 0; i < n-1; i++) {
		for (j = 0; j < n-i-1; j++) {
			if ( nums[j] > nums[j+1]) {
				temp = nums[j];
				nums[j] = nums[j+1];
				nums[j+1] = temp;
			}
		}
	}
}


简单选择排序:第i趟通过n-i-1次比较从序列nums[i...n-1]选择出第i大的数,总共进行n-1趟,时间复杂度:O(N^2),空间复杂度:O(1)。代码如下:

/*Simple select Sort
**第i趟选择出第i小的数
**时间复杂度:O(n^2)
**空间复杂度:O(1)
*/
void simple_select_sort(int *nums, int n) {
	int i, j, min, temp;
	for (i = 0; i < n-1; i++) {
		min = i;
		j = i+1;
		while (j < n) {
			if (nums[j] < nums[min]) {
				min = j;
			}
			j++;
		}
		if (min != i) {
			temp = nums[i];
			nums[i] = nums[min];
			nums[min] = temp;
		}
	}
}

堆排序:用heap_adjust(n/2-1....0)将序列建成最大堆,然后依次将堆顶元素与最大堆的末尾元素交换(这时最后一个元素已不属于堆)并调整序列为最大堆。n-1次调整后,序列有序。最坏情况下时间复杂度:O(nlogn),空间复杂度:O(1)。代码如下:

/*Heap Sort
**堆排序不提倡用于n比较小的序列
**时间复杂度:O(nlogn)
**空间复杂度:O(1)
*/
void heap_adjust(int *nums, int s, int m) { //nums[s...m]中除nums[s]外均满足堆的定义,调整nums[s]使得nums[s...m]成为最大堆
//nums数组下标从0开始
	int j;
	int temp = nums[s];
	for (j = (s+1)*2-1; j <= m; j = (j+1)*2-1) {
		if (j < m && nums[j] < nums[j+1]) ++j; //沿较大的孩子结点向下筛选
		if ( temp >= nums[j]) break;
		nums[s] = nums[j];
		s = j;
	}
	nums[s] = temp;
}
void heap_sort(int *nums, int n) {
//nums数组下标从0开始
	int i, temp;
	for (i = n/2 - 1; i >=0; i--) {
		heap_adjust(nums, i, n-1);
	}
	for (i = n-1; i > 0; i--) {
		temp = nums[i];
		nums[i] = nums[0];
		nums[0] = temp;
		heap_adjust(nums, 0, i-1);
	}
}

void heap_adjust_(int *nums, int s, int m) { //nums[s...m]中除nums[s]外均满足堆的定义,调整nums[s]使得nums[s...m]成为最大堆
//nums数组下标从1开始
	int j;
	int temp = nums[s];
	for (j = s*2; j <= m; j = j*2) {
		if (j < m && nums[j] < nums[j+1]) ++j; //沿较大的孩子结点向下筛选
		if ( temp >= nums[j]) break;
		nums[s] = nums[j];
		s = j;
	}
	nums[s] = temp;
}
void heap_sort_(int *nums, int n) {
//nums数组下标从1开始
	int i, temp;
	for (i = n/2; i >0; i--) {
		heap_adjust(nums, i, n);
	}
	for (i = n; i > 1; i--) {
		temp = nums[i];
		nums[i] = nums[1];
		nums[1] = temp;
		heap_adjust(nums, 1, i);
	}
}<

以下总结是从博友那借鉴来:

按平均时间将排序分为四类:

(1)平方阶(O(n2))排序
     一般称为简单排序,例如直接插入、直接选择和冒泡排序;

(2)线性对数阶(O(nlgn))排序
     如快速、堆和归并排序;

(3)O(n1+£)阶排序
     £是介于0和1之间的常数,即0<£<1,如希尔排序;

(4)线性阶(O(n))排序
     如桶、箱和基数排序。

各种排序方法比较

     简单排序中直接插入最好,快速排序最快,当文件为正序时,直接插入和冒泡均最佳。

影响排序效果的因素

     因为不同的排序方法适应不同的应用环境和要求,所以选择合适的排序方法应综合考虑下列因素:
  ①待排序的记录数目n;
  ②记录的大小(规模);
  ③关键字的结构及其初始状态;
  ④对稳定性的要求;
  ⑤语言工具的条件;
  ⑥存储结构;
  ⑦时间和辅助空间复杂度等。

不同条件下,排序方法的选择

(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
     当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。
     快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
     堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
     若要求排序稳定,则可选用归并排序。但本章介绍的从单个记录起进行两两归并的  排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子文件,然后再两两归并之。因为直接插入排序是稳定 的,所以改进后的归并排序仍是稳定的。

 

排序算法的稳定性

 1) 稳定的:如果存在多个具有相同排序码的记录,经过排序后,这些记录的相对次序仍然保持不变,则这种排序算法称为稳定的。
    插入排序、冒泡排序、归并排序、分配排序(桶式、基数)都是稳定的排序算法。
    2)不稳定的:否则称为不稳定的。
    直接选择排序、堆排序、shell排序、快速排序都是不稳定的排序算法。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值