数据结构:排序

一、选择排序

1.1排序原理

选择排序的实现思路是遍历数组找出最大/最小的值所对应的下标,然后把0下标位置的值与他互换,依此类推,下一次是1位置下标交换,最后实现整个数组的有序

1.2代码实现

依照1.1中原理推理,我们可以得到如下代码(Swap是交换函数,详见补充说明)

void SelectSort1(int* a, int n)
{
	int i = 0;
	
	for (i = 0; i < n; i++)
	{
		
		int gapi = i;
		for (int j = i; j < n; j++)
		{
			if (a[j] < a[gapi])
			{
				gapi = j;
			}
		}
		Swap(&a[i], &a[gapi]);
	}
}

1.3改善代码

自行实现的代码还是有很多缺陷的,比如时间复杂度相对较高,还有缩短空间,可以用mini和maxi代替gapi同时来进行,加快效率。

(过程实现有坑:maxi和begin重叠问题要注意)

void SelectSort(int* a, int n)
{
	int begin = 0;
	int end = n - 1;
	while (begin < end)
	{
		int mini = begin, maxi = begin;
		for (int i = begin+1; i <= end; i++)
		{
			if (a[i] < a[mini])
				mini = i;
			if (a[i] > a[maxi])
				maxi = i;

		}
		Swap(&a[begin], &a[mini]);
		if (maxi == begin)
			maxi = mini;
		Swap(&a[end], &a[maxi]);
		++begin;
		--end;
	}
}

二、堆排序

2.1排序原理

堆排序需要先建堆(物理上是数组存储的堆)

注意升序建大堆,降序建小堆。

建堆完成之后利用先取堆顶数据,然后交换堆顶和堆的末尾,下一次调整减少堆的总数据个数来保存最后一个数据,依此类推实现整个堆的有序。

2.2堆排序前置:向下调整算法

向下调整算法,本质上就是把一个堆顶的数据一层层往下调,升序建大堆,降序建小堆

要建立大堆,最上面的数据要大,所以找到左右孩子里比较大的,如果根节点比大孩子要小
那么换了他们,根节点就同时比左右孩子都大了大了,然后从这个换过的孩子往下接着找

void AdjustDwon(int* a, int n, int root)
{
	int child = root * 2 + 1;
	
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] > a[child])
		{
			child++;
		}
		if (a[root] < a[child])
		{
			Swap(&a[root], &a[child]);
			root = child;
			child = child * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

2.3代码实现

void HeapSort(int* a, int n)
{
	//先建堆,再完成依次交换
	//建堆,树的最后一层不用调,上去就找最后一个结点的父节点,从父节点对应位置调到根节点
	for (int i=(n-1-1)/2;i>=0;i--)
	{
		AdjustDwon(a, n, i);
	}
	//建堆之后才可以调整算法规范位置,此时从最后一个数据开始调,调到最开始的数据,期间
	//配合换位置,总数减一的思路。
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDwon(a, end, 0);
		end--;
	}
}

三、冒泡排序

3.1排序原理

冒泡排序是从第一个数据开始,第一个比第二个,第二个比第三个.......假设我们要排升序,那么过程中,如果我们发现第二个数据比第一个小,那么就需要交换他们的位置,这样以后我们就可以把数组中最大的数据换到数组末尾了。依此类推,下一次的末尾是这一次的前一个,会得到次大的数,循环往复即可使数组有序。

3.2代码实现

void BubbleSort(int* a, int n)
{
	//添加exchange避免已经排好序还在一直走
	
	for (int i = 0; i < n-1; i++)
	{
		int exchange = 0;
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
				Swap(&a[j], &a[j + 1]);
				exchange = 1;
			}
		}
		if (exchange == 0)
			break;
	}
}

四、快速排序(Hoare提出方案)

4.1排序原理

由Hoare提出的快速排序运用了递归的思想,

我们首先说一趟的过程:

4.1.1设置需要的标志

首先传入的使数组左下标left和右下标right,我们要设置一个下标keyi=left,

(设置keyi其实可以是别的,比如keyi=right,但这种情况我们就需要左下标先走,而其他情况会更复杂,此处不做过多讨论)

4.1.2单趟过程

有了这些以后就可以实现大概思路了:用循环找数据,此处假设要排升序,那么

左边排完了是小,右边排完了是大,所以我们要在左边找出大的,右边找出小的把他们换了,

循环往复可以达到目的。

while(left<right)
{
while (left < right && a[right] >= a[keyi])
	right--;
while (left < right && a[left] <= a[keyi])
	left++;

Swap(&a[left], &a[right]);
}

4.1.3完成一轮更换后

经过单趟调整,left和right已经到了同一个位置,也就是keyi应该去的新位置,我们只需要交换他们

	Swap(&a[keyi], &a[left]);
	keyi = left;//更新keyi的值用来递归

4.1.4递归过程

此时我们的数组其实已经成为了以keyi为界限,左侧小于等于它,右侧大于等于他的新数组,

对于新数组我们只要通过递归,再让他们走一遍之前的路即可

QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);

注意添加递归结束条件:

if (left >= right)
	return;

4.2代码实现

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	int begin = left;
	int end = right;
	//设置keyi的位置
	int keyi = left;
	//排升序,左侧找大,右侧找小
	while (left < right)
	{
		//此处必须再限制一遍,避免左向右全是小的,刹不住车,并且要先走右边再走左边
		while (left < right && a[right] >= a[keyi])
			right--;
		while (left < right && a[left] <= a[keyi])
			left++;
		
		Swap(&a[left], &a[right]);
	}
	//走完了,左和右指向同一位置,就是keyi要去的位置(右侧因为刚换过去,一定会挪动一步)
	Swap(&a[keyi], &a[left]);
	keyi = left;//更新keyi的值用来递归
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);

}

五、插入排序

5.1排序原理

插入排序是将第0下标位置的数字视为有序,比如要排升序,只要看它的下一个数应该在这个有序序列的哪一个位置,位置不对就换,对就停下,依次向下走就可以获得一个完全的有序序列

5.2代码实现

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (a[end] > tmp)
			{
				Swap(&a[end], &a[end + 1]);
				end--;
			}
			//如果没有tmp比a[end]小这一情况,就停下
			else {
				break;
			}
		}
        //放入这一数据
		a[end + 1] = tmp;
	}
}

六、希尔排序(插入排序的一种改善)

6.1排序原理

希尔排序作为对插入排序的一种改善,本质上是在分析了插入排序影响效率的因素之后,对这一因素进行的理想型修正,在超多数据排序时会有意想不到的效果。

6.1.1预排序

分组进行插入排序,目的是为了让数组更接近有序,分组的多少受到数组长度n和gap值的影响,gap值即分组标准,比如gap=2,就是说数组第一个值与第三个,第五个...构成同一组,

当gap=1时,就是数组排到有序的时候。

分组完成后对每一组进行插入排序,就是预排序过程。

6.1.2预排序基础上的插入排序

本质上为gap==1时的预排序。

6.2代码实现

void ShellSort(int* a, int n)
{
	int gap = n;
	//对于gap也有说法,只有gap为1的时候才彻底排好,所以循环gap>1
	while (gap > 1)
	{
		gap = gap / 2;


		//起初有两层循环,把第一层和第二层融合为一个

		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > tmp)
				{
					Swap(&a[end], &a[end + gap]);
					end -= gap;
				}
				//如果没有tmp比a[end]小这一情况,就停下
				else {
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

补充说明:

①swap函数是用来交换两个值的,传入两个地址

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

②快速排序中,a[keyi]位置的值一定比left和right相遇位置的值大(只要保证right先走)

可以分left遇到right和right遇到left这两种情况讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值