【算法设计与分析】排序

排序

排序的分类

按是否占用额外内存(原地排序):
out-place归并排序、计数排序、基数排序、桶排序
in-place冒泡排序、插入排序、选择排序、快排、堆排序

按是否为稳定排序分类:
stable冒泡排序、插入排序、归并排序、计数排序、基数排序、桶排序
unstable选择排序(5 8 5 2 9)、快排、堆排序

PS:如何修改不稳定的排序算法使之稳定?
对每个输入的元素加入一个索引index,排序完之后相同的元素按照index再次排序即可。

按时间复杂度:
算法时间复杂度是否基于比较
冒泡、插入、选择O(n^2)
快排、归并O(nlogn)
计数、基数、桶O(n)

总结:

在这里插入图片描述



冒泡排序

特点stable sort、in-place sort
最优复杂度数组已经排好序时:O(n)
最差时间复杂度数组逆序排序时:O(n^2)
平均时间复杂度O(n^2)



插入排序

特点stable sort、in-place sort
最优复杂度数组已经排好序时:O(n)
最差时间复杂度数组逆序排序时:O(n^2)
适合数组类型比较适合“少量元素数组”
插入排序的排序速度是数组中逆序对的个数,逆序越少,插入排序越快
插入排序的过程:

数组的左边有序,右边无序,依次从右边选取数字插入到左边合适的位置。
排序初始时,数组左边为空

Q1.快排是否一定比插入排序快?(数据不随机)
不是的,当数组是顺序排好时,使用快排的时间复杂度是O(n^2),而插入排序的时间复杂度是O(n)
Q2.冒泡排序和插入排序哪个快?
插入排序快。插入排序的速度是逆序对的个数,而冒泡排序中,执行“交换”过程的次数就是逆序对的个数,因此冒泡运行的时间至少是逆序对的个数。


选择排序

最优/最差时间复杂度O(n^2)
平均时间复杂度O(n^2)
空间复杂度O(1)
是否稳定不一定


快速排序

性质in-place sort
最优时间复杂度O(nlogn)
最差时间复杂度数组已经排序时:O(n^2)
特点分治的思想
代码:
void QuickSort(int a[], int l, int r)
{
	if (l >= r)	return;
	int i = l, j = r, key = a[i];

	while (i < j)
	{
		while (i < j && key <= a[j])
			j--;
		a[i] = a[j];
		while (i < j && key >= a[i])
			i++;
		a[j] = a[i];
	}
	a[i] = key;

	QuickSort(a, l, i - 1);
	QuickSort(a, i + 1, r);
}



归并排序

性质out-place sort
最优/最差时间复杂度O(nlogn)
空间复杂度O(n)
特点分治的思想解决问题
代码(C):
void merge(int a[], int l,int r,int mid)
{
	int len = r - l + 1;  //右-左+1
	int *tmp = new int[len]; //开辟len个int型的变量
	int k = 0;
	int i = l;
	int j = mid + 1;    //这里一定要从mid+1开始
	while (i <= mid && j <= r)
		tmp[k++] = a[i] < a[j] ? a[i++] : a[j++];
	while (i <= mid)
		tmp[k++] = a[i++];
	while (j <= r)
		tmp[k++] = a[j++];
	for (k = 0; k < len; k++)
		a[l++] = tmp[k];
}

void merge_sort(int a[], int l, int r)
{
	if (l == r)	return;
	int mid = (l + r) / 2;
	merge_sort(a, l, mid);
	merge_sort(a, mid + 1, r);
	merge(a, l, r, mid);  
}

代码(JAVA):
public void mergeSort(int nums[], int left, int right) {
	if(left == right)	return;
	
	int mid = (left+right)/2;
	
	mergeSort(nums, left, mid);
	mergeSort(nums, mid+1, right);
	
	merge(nums, left, right, mid);
}

public void merge(int[] nums, int left, int right, int mid) {
	int len=right-left+1;
	int[] tmps=new int[len];
	int k=0,i=left,j=mid+1;
	
	while(i<=mid&&j<=right)
		tmps[k++]=nums[i]<nums[j]?nums[i++]:nums[j++];
	while(i<=mid)
		tmps[k++]=nums[i++];
	while(j<=right)
		tmps[k++]=nums[j++];
	
	for(int tmp : tmps) {
		nums[left++] = tmp;
	}
}

Q1.归并排序的缺点是什么?
需要额外的空间(额外的多少空间)
Q2.归并排序和快速排序哪个更快?
快排更快,因为虽然渐近复杂度一样,但是归并排序的系数比快排的大
Q3.归并排序如何改进?
在数组长度为k时使用插入排序(插入排序适合对小数组排序)
其中k=O(logn),此时算法的复杂度为O(nlogn)



堆排序

性质unstable sort、in-place sort
最优/最差时间复杂度O(nlogn)



计数排序(comparison-counting sort)

性质stable-sort,out-place sort
最优/最差时间复杂度O(n^2)
计数排序的过程是,从第一个数开始,记录比它更小的数有多少,这个数字就是它排序完成的数组坐标。



计数排序的延伸还有一个分布计数排序(distribution counting)
考虑数组:

13 11  12   13   12  12

已知他们来自集合{11,12,13}。在这种情况下使用分布计数排序效果较好:
首先统计这些数字出现的频率,然后算出分布值:

数组值111213
频率132
分布值146

排序过程为:
第一个出现的数字是13,排在第5位
分布值更新为:

145

排序数组:

坐标012345
13

第2个出现的数字是11,排在第0位
分布值更新为:

045

排序数组:

坐标012345
1113

第3个出现的数字是12,排在第3位
分布值更新为:

035

排序数组:

坐标012345
111213

第4个出现的数字是13,排在第4位
分布值更新为:

044

排序数组:

坐标012345
11121313

第5个出现的数字是12,排在第2位
分布值更新为:

025

排序数组:

坐标012345
1112121313

最后一个出现的数字是12,排在第1位
分布值更新为:

015

排序数组:

坐标012345
111212121313



桶排序
性质stable sort、out-place sort
最优/最差时间复杂度

Java实现

List<Integer>[] bucket = new List[bucket_length];
for(int i=0;i<bucket_length;i++)
	bucket[i] = new ArrayList<>();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值