【排序算法之后四种排序】(下)

      排序算法最重要的就是自己的思路,那我们必须要理清自己的思路,了解它每一步怎么走,有什么规律,边界情况是怎么控制的等等。接下来我会详细的介绍后四种排序算法,分别是:快速排序(三种方法实现)、归并排序、计数排序、直接选择排序。

 github网址:

diwei00 · GitHubdiwei00 has 4 repositories available. Follow their code on GitHub.https://github.com/diwei00

快速排序:

1)   挖坑法

      让begin和end一个在数组前面,一个在数组后面,同时让pivot在最前面,再找一个关键字key,接下来让end往前找比key小的数字然后放入pivot中,然后pivot在换到end处,begin在往后找比key大的数字,找到后再和pivot换,pivot换到begin处,依次循环,直到begin和end相遇,然后让pivot换到它们相遇处,再让key入坑pivot。这样遍历一遍可以确定一个数据的确切位置,这个时候数组可以认为让pivot分为左区间和右区间,然后就可以去递归左右区间,直到剩一个数就可以认为它是有序的,然后往后返回,一次确定一个数,直到确定完整个数组,即整个数组全部有序。

可以参考这张图进行理解

  

 代码实现(全都有详细注释)

      这里采用了三数取中的方法,(最前面,最后面和中间数的数据中间值)减少采用左右端点碰到极端顺序的出现的最坏情况( 当选取左右端点,碰到数据有序,从大到小或是从小到大的情况 ,算法时间复杂度就会变成最坏时间复杂度)。

void swep(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//三数取中,找最中间的数
int GetMidIndex(int* arr, int left, int right)
{
	int mid = (left + right) >> 1;//找中间数
	if (arr[mid] < arr[right])
	{
		if (arr[left] > arr[right])
		{
			return right;
		}
		else if (arr[left] < arr[right] && arr[left] > arr[mid])
		{
			return left;
		}
		else
		{
			return mid;
		}
	}
	else
	{
		if (arr[left] < arr[right])
		{
			return right;
		}
		else if (arr[left] > arr[right] && arr[left] < arr[mid])
		{
			return left;
		}
		else
		{
			return mid;
		}
	}
}
void QuickSort(int* arr, int left, int right)
{
	
	if (left >= right)//剩一个数认为有序,返回
	{
		return;
	}
	int index = GetMidIndex(arr, left, right);//三位取中,防止最坏情况
	swep(&arr[left], &arr[index]);//将取到的数放到最前面

	int begin = left;
	int end = right;
	int pivot = left;
	int key = arr[begin];//关键字
	while (begin < end)
	{
		while (begin<end && arr[end] >= key)//找小
		{
			end--;
		}
		arr[pivot] = arr[end];
		pivot = end;

		while (begin < end && arr[begin] <= key)//找大
		{
			begin++;
		}
		arr[pivot] = arr[begin];
		pivot = begin;
	}
	pivot = begin;
	arr[pivot] = key;
	QuickSort(arr, left, pivot - 1);//递归左
	QuickSort(arr, pivot + 1, right);//递归右
}

2)  前后指针法

      让prev指向第一个数据,cur指向第二个数据,再找一个关键字key,然后让cur向前遍历数组数据,找到比key小的数据,让prev往前走一步,接着让prev和cur交换,这样就可以保证把小的数据换到前面,大的数据换到后面,当cur找到最后一个数据,最后把key放到prev处。这样排序完一趟也可以具体确定一个数据的确切位置,然后就可以认为,数组中数据被prev分为左右区间,接着去递归左右区间。也是同样的道理,直到递归到剩一个数据就认为有序,接着往后返回,直到所有数据有序。

可以参考图片进行理解

代码实现: (采用三数取中的方法)

void QuickSort1(int* arr, int left, int right)
{
	if (left >= right)//剩一个数据认为有序,返回
	{
		return;
	}
    int index = GetMidIndex(arr, left, right);//三位取中,防止最坏情况
	swep(&arr[left], &arr[index]);//将取到的数放到最前面
	
	int prev = left;//后驱指针
	int cur = left + 1;//前驱指针
	int key = arr[left];//关键字
	while (cur <= right)
	{
		if (arr[cur] <= key)//cur找小
		{
			prev++;
			swep(&arr[prev], &arr[cur]);
		}
		cur++;
	}
	swep(&arr[left], &arr[prev]);

	QuickSort1(arr, left, prev - 1);//递归左区间
	QuickSort1(arr, prev + 1, right);//递归右区间
}

 3)左右指针法

      先定义一个左指针begin,和右指针end,再选一个关键字key,让左指针向后遍历,找比key大的数据,让右指针向前遍历,找比key小的数据,两个指针都找到后,然后交换数据。这样也可以把小的数据换到前面,把大的数据换到后面,当begin和end相遇后,把key放到begin指针处。当这样排完一趟后也可以确定一个确切位置的数据,同时也就可以认为数组数据被begin分为左右两个区间,然后去递归左右区间,直到剩一个数据认为有序,然后返回,一次可以确定一个确切一个数据,直到整个递归到整个数组有序。

可以参考图片进行理解:

 代码实现: (采用三数取中的方法)

void QuickSort1(int* arr, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int index = GetMidIndex(arr, left, right);//三位取中,防止最坏情况
	swep(&arr[left], &arr[index]);//将取到的数放到最前面
	int prev = left;//后驱指针
	int cur = left + 1;//前驱指针
	int key = arr[left];//关键字
	while (cur <= right)
	{
		if (arr[cur] <= key)//cur找小
		{
			prev++;
			swep(&arr[prev], &arr[cur]);
		}
		cur++;
	}
	swep(&arr[left], &arr[prev]);

	QuickSort1(arr, left, prev - 1);//递归左区间
	QuickSort1(arr, prev + 1, right);//递归右区间
}

归并排序:

      先把数组数据从中间分为左区间begin1到end1,和右区间begin2到end2.前提是左右区间都有序,然后开辟一块同样大小的数组,左右区间同时进行归并,从begin1和begin2开始比,把小的数据往新开辟的数组里放,这样新数组里的数据都是有序的。两个区间总有一个先结束,然后把剩下的数组里的数据拷入新数组,即新数组里的数据全部都有序。最后把新数组里的数据拷入原数组。但是要保证左右区间里的数据都有序,就可以考虑用递归的方法,递归左右区间,直到左右区间剩一个数据,即认为它是有序的。然后往上返回,一步步的递归到原始左右区间都是有序的,然后在进行最终的归并,即整个数组数据都是有序的。

可以参考图片理解:

   

代码实现:

void _MergeSort(int* arr, int left, int right, int* tmp)
{
	if (left >= right)//生一个值认为有序,返回
	{
		return;
	}

	int mid = (left + right) >> 1;//找到中间区间
	_MergeSort(arr, left, mid, tmp);//递归左区间
	_MergeSort(arr, mid + 1, right, tmp);//递归右区间
	int begin1 = left;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = right;
	int index = left;//控制新数组的指针
	
	while (begin1 <= end1 && begin2 <= end2)//左右区间同时进行
	{
		if (arr[begin1] < arr[begin2])//找小的数据往新数组里放
		{
			tmp[index++] = arr[begin1++];

		}
		else
		{
			tmp[index++] = arr[begin2++];
		}
	}
	//总有一个数组先结束,把没结束数据数据拷入新数组
	while (begin1 <= end1)
	{
		tmp[index++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = arr[begin2++];
	}
	//将新数组数据拷回原数组
	int i = 0;
	for (i = 0; i <= right; i++)
	{
		arr[i] = tmp[i];
	}
}
void MergeSort(int* arr, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		return;
	}
	_MergeSort(arr, 0, n - 1, tmp);//防止malloc重复开辟,在内部进行递归
	free(tmp);
}

.

选择排序:

      选择排序是先选大的数据往后面放,在选次大的数据往后放,依次循环,直到整个数组数据全部遍历完。这里采用了一个优化的版本,先定义一个左指针begin,和右指针end,然后遍历数组,找最小的数据和最大的数据,找到后小数据放最前面begin处,大数据放最后面end处,依次循环找次大和次小的数据,直到左右指针相遇结束。这样就可以把小数据往前放,大数据往后放,即最后整个数组数都是有序的。

可参考图片进行理解:

代码实现:

void SelectSort2(int* arr, int n)
{
	int begin = 0;//前指针
	int end = n - 1;//后指针
	while (begin < end)
	{
		int max = begin;
		int min = begin;
		int i = 0;
		for (i = begin; i <= end ; i++)
		{
			if (arr[i] > arr[max])//找大
			{
				max = i;
			}
			if (arr[i] < arr[min])//找大
			{
				min = i;
			}
		}
		swep(&arr[begin], &arr[min]);//小的换前面
		swep(&arr[end], &arr[max]);//大的换后面
		begin++;
		end--;
	}
}

计数排序:

      先把一组数据的数据进行遍历,然后开辟一块同样大小的数组,数据全部初始化为0。原数组的数据是几就给新开辟数组对应下标的值进行加1,即绝对映射。这样就可以进行计数。因为数据值可能是比较大的一段区间。为了节省空间,考虑用相对映射的方式,即每个数据都减去一个最小的数据,然后在对应相应的数组下标值进行加1计数。最后再把记的数据的下标从左到右依次相对应的个数,还原到原数组。

可参考图片进行理解:

代码实现: 

void CountSort(int* arr, int n)
{
	int max = arr[0];
	int min = arr[0];
	int i = 0;
	for (i = 0; i < n; i++)
	{
		if (arr[i] > max)//找最大的数据
		{
			max = arr[i];
		}
		if (arr[i] < min)//找最小的数据
		{
			min = arr[i];
		}
	}
	int range = max - min + 1;//确定区间,方便相对映射
	int* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)
	{
		return;
	}
	memset(count, 0, sizeof(int) * range);//初始化开辟数组数据为0
	//统计每个数据的次数
	int j = 0;
	for (j = 0; j < n; j++)
	{
		count[arr[j] - min]++;//相对映射位置
	}

	//按次数还原回去,数据还原的是下标
	i = 0;
	int k = 0;
	for (k = 0; k < range; k++)
	{
		while (count[k]--)
		{
			arr[i++] = k + min;
		}
	}
	free(count);
}

小结: 

      八大排序到此完结,提示一下:我们一定要把握好自己的思路。还有一个基数排序,这个不是很实用,后期再写。希望老铁们有所收获。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小太空人w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值