直接选择排序 堆排序 快速排序及优化

发布于:2022-12-17 ⋅ 阅读:(579) ⋅ 点赞:(0)

 一.直接选择排序

直接选择排序是所有排序方法中,思想最简单,最直观的排序方法,每次选待排序列中选择最大或者最小的数,放在整个序列的起始位置,话不多说,直接上code,

void SlectSort(int* arr, int size)
{
	int j = 0;
	for(j; j < size - 1; j++)
	{
        //将初始的最小值设置为待排序列的第一个元素
		int min = j;
        //从第二个元素开始遍历,找最小的值
		int i = min + 1;
		for (i; i < size; i++)
		{
			if (arr[i] < arr[min])
			{
				min = i;
			}
		}
		int tmp = arr[min];
        //向后移动,插入
		BackMove(arr + j, min - j);
		arr[j] = tmp;
	}
}



void BackMove(int* pa, int size)
{
	memmove(pa + 1, pa, size * sizeof(int));
}

二.堆排序

堆排序是对简单选择排序的改进,我们发现在简单选择排序中,每次找最大或者最小值时,重复比较的次数太多,有没有一种办法,可以将之前比较过的记录进行一次痕迹存储?因此堆排序应运而生。

关于堆的概念即性质,可以在二叉树章节补。在这里只做简单总结,堆可以分为两种,小根堆和大根堆,小根堆即父节点的value小于等子节点value的完全二叉树,与之对应,大根堆是父节点的value大于等于子节点value的完全二叉树。

堆排序的具体步骤:

1.将待排序的序列构造成一个堆(升序构造大根堆,降序构造小根堆)。

2.将堆顶的元素(最大或者最小值)与堆底的元素交换,然后--size,再将剩余的元素调整成堆,即又找的次大的元素,直到堆中只剩一个元素。

3.该排序的核心点在于调整,本文采用的是,向下调整。

所谓向下调整,就是从最后一个节点的父亲节点开始(因为叶子节点度为0,不需要调整),向下调整,没调整一次后,--parent。直到parent成根节点。每一次交换事实上是,把堆顶最大或者最小的元素交换到最后,这也是为何升序采用大根堆,降序采用小根堆的本质原因。

code(以升序为例):

void HeapSort(int* arr, int size) 
{
	//构造大根堆
	int parent = (size - 2) / 2;
	
	while (parent >= 0)
	{
		AdjustDown(arr, size, parent--);
	}
	//反复交换堆顶堆底然后从堆顶向下调整,只剩一个元素时结束
	while (size > 2)
	{
		Swap(arr, arr + size - 1);
		AdjustDown(arr, --size, 0);

	}
}

void AdjustDown(int* arr, int size, int parent)
{
	assert(parent < size);
	int left_child = parent * 2 + 1;
	int right_child = (parent + 1) * 2;
	int max = left_child;

	if (arr[left_child] < arr[right_child])
	{
		++max;
	}
	while (max < size)
	{
		if (arr[max] > arr[parent])
		{
			Swap(arr + max, arr + parent);
			parent = max;
			max = parent * 2 + 1;
			if (arr[max] < arr[max + 1])
			{
				++max;
			}
		}
		else
		{
			return;
		}
	}
}
void Swap(int* pa, int* pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

三.基础的快速排序

以升序为例,基本思路如下:

1.选取一个key值(基准),一般是序列中第一个数or最后一个数

2.定义两个指针,left和right,分别指向序列的首尾

3.right先走,right找比key小的数,left找比key大的数,找到以后将left和right交换。
4.当left和right相遇后,则将key与相遇的值交换,最终完成一次排序,将整个序列分成了以key值为基准的左右两部分(左边比key小,右边比key大)

5.完成一次排序以后,则以同样的思路去递归排序左边和右边的序列。

CODE:

void QuickSort(int* arr, int size)
{
	if (size <= 10)
	{
		InsertSort(arr, size);
		return;
	}
	if (size == 0 || size == 1)
	{
		return;
	}
	int key = *arr;
	int left = 0;
	int right = size - 1;
	while (left < right)
	{
		if (arr[right] < key)
		{
			if (arr[left] > key)
			{
				Swap(arr + left, arr + right);
				--right;
			}
			else
			{
				++left;
			}
		}
		else
		{
			--right;
		}
	}
	Swap(arr, arr + left);
	int line = left;
	QuickSort(arr, line);
	QuickSort(arr + line + 1, size - line - 1);
}

四.快速排序的“挖坑”优化

right先走,遇到比key小的值,则直接与key交换;然后left再开始动,遇到比key大的数则直接与key交换,当left和right相遇时,key值已经自己到它该去的位置上了。

CODE:

void HoleOptim(int* arr, int size)
{
	int key = 0;
	int left = 0;
	int right = size;
	while (left < right)
	{
		do
		{
			--right;
		} while (left < right && arr[right] >= arr[key]);
		if (left < right)
		{
			Swap(arr + right, arr + key);
			key = right;
		}
		do
		{
			++left;
		} while (left < right && arr[left] <= arr[key]);
		if (left < right)
		{
			Swap(arr + left, arr + key);
			key = left;
		}
	}
}

五.快速排序的三数取中优化

最好的情况时,选到的key值恰好是整个序列的中位数;所以三数取中优化的目的,是为了选择一个相对中间的值作为key值。在这里,我们取序列中的首 尾 以及中间位置的值,然后从他们三个中选出中间大小的值作为Key,这里需要注意的是,快速排序牵扯到交换元素的位置,所以我们这里的优化最终要返回key的下标。

CODE:

int GetMidIndex(int* arr, int size)
{
	int mid = 0;
	if (arr[0] > arr[size - 1])
	{
		mid = (arr[size - 1] >= arr[size / 2] ? size - 1 : size / 2);
	}
	else if(arr[0] < arr[size - 1])
	{
		mid = (arr[0] <= arr[size / 2] ? size / 2 : 0);
	}
	else
	{
		mid = 0;
	}
	return mid;
}

本文含有隐藏内容,请 开通VIP 后查看