各种排序不为人知的真相

常用的排序算法有:

交换排序(冒泡排序、快速排序)

选择排序(直接选择排序、堆排序)

插入排序(直接插入排序、二分插入排序、希尔排序)

归并排序、

基数排序

直接选择排序和冒泡排序的比较

void bubblesort(vector<int>& list)
{
	for (int i = 0; i < list.size(); i++)
	for (int j = 0; j < list.size() - i; j++)
	{
		if (list[j] > list[j + 1])
			swap(list[j], list[j + 1]);
	}
}

void choosesort(vector<int>& list)
{
	int min, minP;
	for (int i = 0; i < list.size(); i++)
	{
		min = list[i];
		for (int j = i; j < list.size(); j++)
		{
			if (min > list[j])
			{
				min = list[j];
				minP = j;
			}
		}
		swap(list[i], list[minP]);
	}
}

两种排序方法的平均和最坏时间复杂度是一样的都是O(n2),冒泡排序的最好时间复杂度是O(n),直接选择排序的最好时间复杂度也是O(n2)。一般来说选择比冒泡效率高(单从时间复杂度来考虑是看不出来的),从上面代码可以看出冒泡排序的交换操作在内层,而选择排序在外循环中进行交换操作。冒泡的话如果不这么频繁交换则需要记录坐标(牺牲空间复杂度),还有一点就是可以检测整个数组是否已经有序,即当某次遍历没有发生任何交换的时候终止算法。

插入排序:就是每步将一个待排序的记录,按顺序插入到前面已经排好序的序列中。直接插入排序就是从后往前挨个比较,二分插入的话就是运用二分法将待排序记录插入。

void insertsort(vector<int> list)
{
	//外层循环表示待排序记录
	for (int i = 1; i < list.size(); i++)
	for (int j = 0; j < i; j++)
	{
		//内层循环实现插入
		if (list[j] > list[i])
			swap(list[j], list[i]);
		else
			break;
	}
}

int binary_search(const vector<int>& list, int start, int end, int list_i)
{
	while (start <= end)
	{
		int middle = (start + end) / 2;
		int middle_data = list[middle];
		if (middle_data > list_i)
			end = middle - 1;
		else
			start = middle + 1;

	}
	return start;
}
void b_insertsort(vector<int> list)
{
	for (int i = 1; i < list.size(); i++)
	{
		if (list[i] < list[i - 1])
		{
			//得到插入位置的索引
			int index = binary_search(list, 0, i, list[i]);
			//移动元素,从index到i
			for (int j = i - 1; j >= index; j--)
				swap(list[j + 1], list[j]);
		}
	}
}

希尔排序是一种分组插入排序,通过将增量逐渐减小将逆序记录消除。

void shellsort(vector<int> list)
{
	int j;
	//设置初始步长
	int num = list.size();
	num /= 2;
	vector<int> tmp;
	while (num >= 1)
	{
		//先根据步长分组插入排序
		for (int i = num; i < list.size(); i++)
		{
			//将list[i]插入同组已经排好序的序列
			int tmp = list[i];
			j = i - num;
			//如果小往前比
			while (j >= 0 && tmp < list[j])
			{
				list[j + num] = list[j];
				j -= num;
			}
			list[j + num] = tmp;
		}
		num /= 2;
	}
}

希尔排序是最先突破O(n2)的排序算法的一批,排序的本质是消除逆序数,冒泡、直接插入排序等每次只能消除一个,所以复杂度都是O(n2),而比较快的如快排、希尔、堆排都是交换比较远的记录,使得一次交换能够消除一个以上的逆序,只不过规则不同。

快速排序属于交换排序,是冒泡排序的一种改进。基本思想是通过一趟排序将要排序的记录分为两个部分,然后分别运用快排进行排序。从显式的感觉上看,快排的快的有道理的,快排有远距离交换,他每扫一遍待排序序列都会更有序,大的小的分开在两边。

源码:

//递归版
int sort(vector<int>& list, int low, int high)
{
	//选首元素作为轴
	int tmp = list[low];
	while (low < high)
	{
		while (list[high] >= tmp)
			high--;
		list[low] = list[high];
		while (list[low] <= tmp)
			low++;
		list[high] = list[low];
	}
	list[low] = tmp;
	return low;
}

void quicksort(vector<int>& list, int low, int high)
{
	int mid = sort(list, low, high);
	quicksort(list, low, mid - 1);
	quicksort(list, mid, high);
}

接下来到堆排序同志了,在这之前先介绍一下他的简配版----树形选择排序,也叫锦标赛排序,基本思想就是锦标赛的晋级机制(8进4、半决赛、决赛)。

首先将待排序记录两两比较,然后取较小的记录继续进行比较(这里以升序排列为例),这样就会得到一个有n个叶子节点的完全二叉树,如图:

然后开始第二阶段,输出根节点,即得到最终结果的第一个记录。这时从叶子节点找到和最小值相同的记录,把这个记录当成最大开始比较得到次小值,如图:(按比赛的思想来说就是找出被他打败的选手里哪个更强)

重复第二阶段可以完成排序。由于n个叶子节点的完全二叉树的深度是log2n+1,所以每选一次需要进行log2n次比较,n个点的时间复杂度就是nlog2n;但是这种方法需要的辅助存储空间多,而且有一些最大值多余的比较。为了做一下优化,堆排序就诞生了。

堆排序刚开始也是构建一个完全二叉树,只不过这里不会有重复记录了,即先按照初始序列自顶向下写出来就好了,然后按需求(升序还是降序)选择调整成最大堆还是最小堆,这里以降序构造最大堆为例进行说明。最小堆就是这棵树的所有父节点都比自己的子节点大。

构造最大堆是从最后一个非叶子节点开始,从右往左从下往上地调整。如图,初始序列[16,7,3,20,17,8]

从3开始,3-8不满足最大堆定义,交换。

7不满足,将7和他最大子节点交换。

16不满足,将16和20交换。

换完之后16不满足,16和17交换。

最大堆生成:

第二阶段:输出堆顶元素,将堆顶元素和最后一个元素交换,调整堆。

3和20交换,3/17/8不满足,选8和17中较大的和3交换,此时3/7/16又不满足了,继续交换得到图3,此时最大堆又出现了。

继续重复操作,完成排序:

堆排序源码:

//堆调整
void heapadjust(vector<int>& list, int s, int length)
{
	//s为当前字数的临时堆顶,先暂存到temp
	int maxTemp = s;
	//s的左儿子2*s+1、右儿子2*s+2
	int sLchild = 2 * s + 1;
	int sRchild = 2 * s + 2;
	if (s <= length / 2)
	{
		//如果左儿子比较大,调整
		if (sLchild <= length && list[sLchild] > list[maxTemp])
		{
			maxTemp = sLchild;
		}
		//如果右儿子比较大,调整
		if (sRchild <= length && list[sRchild] > list[maxTemp])
		{
			maxTemp = sRchild;
		}
		//如果有调整,交换list记录maxTemp,s
		if (list[maxTemp] != list[s])
		{
			swap(list[maxTemp], list[s]);
			heapadjust(list, maxTemp, length);
		}
	}
}

//初建堆
void buildheap(vector<int>& list)
{
	//有n个节点的完全二叉树,最后一个非叶节点是n/2-1,i--则遍历了所有非叶节点,堆调整后变大顶堆
	for (int i = list.size() / 2 - 1; i >= 0; i--)
	{
		heapadjust(list, i, list.size());
	}
}

void heapsort(vector<int> list)
{
	buildheap(list);
	for (int i = list.size() - 1; i >= 1; i--)
	{
		//每次将堆顶记录和当前未排序的最后一个记录交换
		swap(list[i], list[0]);
		//调整新的大顶堆
		heapadjust(list, 0, i);
	}
}

还剩归并排序和基数排序,为了更好地阅读体验(其实是我有点乱了先写到这吧,哈哈闭嘴)写到下一篇吧。

一定要看哦!!!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值