浅谈一下排序

目录

1.0:辅助函数

 2.0:冒泡排序

3.0:插入排序

4.0:希尔排序

5.0:选择排序

6.0:堆排序

7.0:快速排序

7.1:递归

7.1.1:三数取中

7.1.2:霍尔思想

7.1.3:挖坑法

7.1.4:前后指针法

7.1.5:快速排序递归式函数

 7.1.6:小优化

7.2:非递归

8.0:计数排序

9.0:归并排序

9.1:递归

9.2:非递归


1.0:辅助函数

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
typedef int HPDataTroy;
//交换
void Swap(HPDataTroy* p1, HPDataTroy* p2) {
	HPDataTroy* tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//打印函数
void SortPrint(HPDataTroy* a, int n) {
	assert(a);
	for (int i = 0; i < n; i++) {
		printf("%d ",a[i]);
	}
	printf("\n");
}

 因为排序需要较多的交换操作和打印测试,所以我们直接写成函数,后期调用即可。

 2.0:冒泡排序

//冒泡排序
void BubbleSort(HPDataTroy* a, int n) {
	assert(a);
	int count = 0;
	for (int i = 0; i < n; i++) {
		for (int j = 1; j < n-i; j++) {
			if (a[j - 1] > a[j]) {
				count = 1;
				Swap(&a[j-1],&a[j]);
			}
		}
		if (count == 0) {
			break;
		}
	}
}

 冒泡排序思想:对这个长度为n的数组,我们进行相邻两个元素之间的比较,一次这样的操作就可以把最大的元素放到最后,以此类推,n-1次这样的重复操作即可把长度为n的数组进行有序化操作。我们也可以对冒泡排序进行一次优化,即如果在某一次遍历的过程中,前一个元素一直小于后一个元素,如果这样的话,我们的数组已经完成了有序化,中止剩余循环即可。

3.0:插入排序

//插入排序
void InsertionkSort(HPDataTroy* a, int n) {
    assert(a);
	for (int i = 0; i < n-1; i++) {
	//单趟
	int cur =i;
	int end = a[cur+1];
	while (cur >= 0) {
		if (a[cur] > end) {
			a[cur + 1] = a[cur];
			cur--;
		}
		else {
			break;
		}
	}
	a[cur + 1] = end;
	}
}

 插入排序思想:对于长度为n的数组,我们首先存储一个下标值(cur),存储下标值的下一个元素的值(简称end),然后通过下标值索引元素让它和存储的下一个元素进行对比,如果前者大,那就覆盖后面的元素,然后下标值向前走(cur-=1),一直到下标值索引元素小于end时,或者cur=-1时,单趟遍历结束,将cur+1位置的值替换成end即可,所以,我们需要控制cur从0到n-1即可完成对整体的控制。

4.0:希尔排序

//希尔排序
void ShellSort(HPDataTroy* a, int n) {
	assert(a);
	int gap = n;
	while (gap > 0) {
	gap /= 2;//也可以写为gap=gap/3+1;
	//注意:当gap=1时,就是插入排序,gap!=1时,为预排序
		for (int j = 0; j < gap; j++) {
			for (int i = j; i < n - gap; i += gap) {
			int cur = i;
			int end = a[i + gap];
				while (cur >= 0) {
					if (a[cur] > end) {
						a[cur + gap] = a[cur];
						cur -= gap;
					}
					else {
						break;
					}
				}
			a[cur + gap] = end;
			}
		}
	}
}

希尔排序分为两步:

1.预排序 

2.插入排序

 希尔排序思想:希尔排序在插入排序的基础上,通过用数组长度n来确定gap的预排序,使得相邻gap长度的元素有序,通过有限次的预排序,最后配合一次插入排序(gap=1时),即可对数组进行有序化。

5.0:选择排序

//选择排序
void SelectSort(HPDataTroy* a, int n) {
	assert(a);
	int begin1 = 0;
	int end1 = n-1;
	while (begin1 < end1) {
		int begin2 = begin1;
		int end2 = begin1;
		//遍历找大找小
		for (int i = begin1; i <= end1; i++) {
			//找小
			if (a[i] < a[begin2]) {
				begin2 = i;
			}
			//找大
			if (a[i] > a[end2]) {
				end2 = i;
			}
		}
		Swap(&a[begin1], &a[begin2]);
		if (begin1 == end2) {
			end2 = begin2;
		}
		Swap(&a[end1], &a[end2]);
		begin1++;
		end1--;
	}
}

 选择排序思想:我们通过begin1和end1控制数组的范围,在可控的范围内,找到最大的和最小的元素的下标,然后交换begin1和begin2、end1和end2,最后我们通过begin1和end1逐次减少数组长度即可控制数组有序化。

6.0:堆排序

//向下调整
void AdjustDown(HPDataTroy*a,int n,int parent) {
	assert(a);
	int child = parent * 2 + 1;
	while (child < n) {
		//找到两个孩子中较大的那个
		if (child + 1 < n && a[child + 1] > a[child]) {
			child++;
		}
		if (a[child] > a[parent]) {
			Swap(&(a[child]), &(a[parent]));
			parent = child;
			child = parent * 2 + 1;
		}
		else {
			break;
		}
	}
}
//堆排序
void HeapSort(HPDataTroy* a, int n) {
	assert(a);
	//建大堆-向下调整
	for (int i = (n - 1 - 1) / 2; i >= 0; --i) {
		AdjustDown(a,n,i);
	}
	//利用删除的思想排升序
	int end = n - 1;
	while (end > 0) {
		Swap(&(a[0]), &(a[end]));
		AdjustDown(a, end, 0);
		end--;
	}
}

 堆排序思想:我们将这个数组进行向下调整建大堆之后,此时,堆顶元素就是最大的元素,我们利用删除的思想,将最大的元素与第一个元素交换,并进行一次向下调整,向下调整元素个数依次递减(第一次为n-1,以此类推),用end控制让其走到0的位置即可完成数组有序化。

7.0:快速排序

7.1:递归

7.1.1:三数取中

//三数取中
int Threenums(HPDataTroy* a, int begin, int end) {
	int keyi = (begin + end) / 2;
	if (a[begin] < a[end]) {
		//a[begin]  a[end]
		if (a[keyi] < a[begin]) {
			//a[keyi] a[begin] a[end]
			return begin;
		}
		else if (a[keyi] > a[end]) {
			//a[begin] a[end] a[keyi]
			return end;
		}
		else {
			//a[begin] a[keyi] a[end]
			return keyi;
		}
	}
	else {
		//a[end] a[begin]
		if (a[keyi] < a[end]) {
			//a[keyi] a[end] a[begin]
			return end;
		}
		else if (a[keyi] > a[begin]) {
			//a[end] a[begin] a[keyi]
			return begin;
		}
		else {
			//a[end] a[keyi] a[begin];
			return keyi;
		}
	}	
}

 对于快速排序而言,我们都希望第一个元素的值尽可能为中间值,这样有利于缩减递归的层数,达到效率优化。

7.1.2:霍尔思想

//霍尔思想
int Past1(HPDataTroy* a, int begin, int end) {
	int midi = Threenums(a,begin,end);
	Swap(&a[begin], &a[midi]);
	int keyi = begin;
	while (begin < end) {
		//右侧找小
		while (begin<end && a[end]>=a[keyi]) {
			end--;
		}
		//左侧找大
		while (begin < end && a[begin] <= a[keyi]) {
			begin++;
		}
		Swap(&(a[begin]), &(a[end]));
	}
	Swap(&a[begin],&a[keyi]);
	return begin;
}

 霍尔大佬的思想:先记录首元素的位置,再用两个一前一后指针,先让后指针前移找小的元素,然后让前指针后移找到大的元素,注意:这里的大和小都是相对于第一个元素值而言,如果两指针相遇,则交换第一个位置的元素和相遇位置的元素。若没有相遇,则前后指针所对应的元素值交换后,再重复相同的找大找小操作,直到相遇,执行对应操作。此时,相遇的位置的元素值就是排序完成后该元素所出现的最终位置。

7.1.3:挖坑法

//挖坑法
int Past2(HPDataTroy* a, int begin, int end) {
	int midi = Threenums(a, begin, end);
	Swap(&a[begin], &a[midi]);
	int hole = begin;
	int tmp = a[hole];
	while (begin < end) {
		while (begin < end && a[end] >= a[hole]) {
			end--;
		}
		a[hole] = a[end];
		hole = end;
		while (begin < end && a[begin] <= a[hole]) {
			begin++;
		}
		a[hole] = a[begin];
		hole = begin;
	}
	a[hole] = tmp;
	return hole;
}

 挖坑法的思想在于,起始给定一个坑位(第一个元素的位置),利用一前一后指针,后指针找小,然后将小的元素值覆盖给坑位的元素值,坑位此时就到了后指针的位置,然后前指针同理操作,最后将坑位位置的元素值覆盖成起始坑位的值(tmp)即可,最终,坑位位置的值就是排序完成后该元素所出现的最终位置。

7.1.4:前后指针法

//前后指针法
int Past3(HPDataTroy* a, int begin, int end) {
	int midi = Threenums(a, begin, end);
	Swap(&a[begin], &a[midi]);
	int prev = begin;
	int cur = prev + 1;
	int tmp = begin;
	while (cur <= end) {
		if (a[cur] < a[tmp]) {
			prev++;
			Swap(&(a[prev]), &(a[cur]));
		}
		cur++;
	}
	Swap(&a[prev], &(a[tmp]));
	return prev;
}

 前后指针的思想在于利用前后指针,前指针所指向的值如果小,那么后指针向前走一步,交换前后指针指向的值,最后,前指针向前走一步,如果前指针所指向的值大,那么后指针不做任何操作。最终,前指针遍历结束之后,交换后指针与起始后指针所指向的值(tmp位置)即可。之后,后指针位置的值就是排序完成后该元素所出现的最终位置。

7.1.5:快速排序递归式函数

//快速排序
void QuickSort(HPDataTroy* a, int begin, int end) {
	assert(a);
	if (begin >= end) {
		return;
	}
	int keyi = Past1(a,  begin, end);
	//int keyi = Past2(a, begin, end);
	//int keyi = Past3(a, begin, end);

	//[begin,keyi-1] keyi [keyi+1,end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi+1, end);
}

 7.1.6:小区间优化

//快速排序
void QuickSort(HPDataTroy* a, int begin, int end) {
	assert(a);
	if (begin >= end) {
		return;
	}
	if (end - begin + 1 < 10) {
		int keyi = Past1(a, begin, end);
		//int keyi = Past2(a, begin, end);
		//int keyi = Past3(a, begin, end);

		//[begin,keyi-1] keyi [keyi+1,end]
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
	else {
		InsertionkSort( a, end-begin+1);
	}
}

 如果递归后数组长度大于10,那么快速排序效率高,如果小于10,此时就可以用一个插入排序,节省空间,提高效率。

7.1.7:三路划分

//三路划分
	int left = begin;
	int right = end;
	int cur = left + 1;
	int key = a[left];
	while (cur <= right) {
		if (a[cur] < key) {
			Swap(&a[cur], &a[left]);
			cur++;
			left++;
		}
		else if (a[cur] > key) {
			Swap(&a[cur], &a[right]);
			right--;
		}
		else {
			cur++;
		}
	}
//[begin,left-1] [left,right] [right+1,end]

 三路划分将以第一个值为标准,将小的换到左边,相等的换到中间,大的换到右边,三路划分之后,我们只需要递归左边和右边排序即可。

最终:快速排序递归算法:三数取中+三路划分+小区间优化

7.1.8:最终优化代码

void QuickSort2(int* a, int begin, int end) {
	if (begin >= end) {
		return;
	}
	//小区间优化
	if (end - begin + 1 < 10) {
		//三数取中
		int midi = Threenums(a, begin, end);
		Swap(&a[begin], &a[midi]);
		//三路划分
		int left = begin;
		int right = end;
		int cur = left + 1;
		int key = a[left];
		while (cur <= right) {
			if (a[cur] < key) {
				Swap(&a[cur], &a[left]);
				cur++;
				left++;
			}
			else if (a[cur] > key) {
				Swap(&a[cur], &a[right]);
				right--;
			}
			else {
				cur++;
			}
		}
		//[begin,left-1] [left,right] [right+1,end]
		QuickSort2(a, begin, left - 1);
		QuickSort2(a, right + 1, end);
	}
	else {
		InsertSort(a + begin, end - begin + 1);
	}
}

7.2:非递归

//快速排序---非递归
void QuickSortNonR(int* a, int begin, int end) {
	//创建栈
	ST s;
	//初始化
	Init(&s);
	//入尾部位置
	Push(&s,end);
	//入头部位置
	Push(&s,begin);
	//只要栈不为空,循环继续
	while (!determine(&s)) {
		//读取栈顶元素
		int left= obtain(&s);
		Pop(&s);
		int right = obtain(&s);
		Pop(&s);
		int keyi = Past1(a, left, right);
		//[left keyi-1]   keyi   [keyi+1  right]
		//如果成区间,那就入栈
		if (keyi + 1 < right) {
			Push(&s, right);
			Push(&s, keyi+1);
		}
		if (left<keyi-1) {
			Push(&s, keyi-1);
			Push(&s, left);
		}
	}
	//销毁
	Distroy(&s);
}

非递归主要借助与栈,利用栈的后进先出的特点,记录区间,从而避免递归的空间浪费,达到优化效果。栈的内容可以查阅本人之前发布的文章查看。

8.0:计数排序

//计数排序
void CountSort(HPDataTroy* a,int n) {
	int min = a[0];
	int max = a[0];
	//遍历找最大和最小元素
	for (int i = 0; i < n; i++) {
		//找最小
		if (a[i] < min) {
			min = a[i];
		}
		//找最大
		if (a[i] > max) {
			max = a[i];
		}
	}
	//开空间
	HPDataTroy* tmp = (HPDataTroy*)malloc(sizeof(HPDataTroy) * (max-min+ 1));
	if (tmp == NULL) {
		perror("malloc fail");
		exit(-1);
	}
	//初始化空间
	for (int i = 0; i < (max - min + 1); i++) {
		tmp[i] = 0;
	}
	//计数
	for (int i = 0; i < n; i++) {
		tmp[a[i]-min]++;
	}
	//写入
	int index = 0;
	for (int i = 0; i < (max - min + 1); i++) {
		while (tmp[i]) {
			a[index] = i + min;
			index++;
			tmp[i]--;
		}
	}
}

 计数排序思想:将每个元素出现的次数记录在一个新的空间中,利用新空间将原数组每个值出现的次数记录下来,再将这个空间每个位置的值的下标来覆盖掉原数组的值,额外开辟空间的对应的值就是每次循环的次数。

9.0:归并排序

9.1:递归

//递归子函数
void _MergerSort(HPDataTroy* a, HPDataTroy* tmp, int begin, int end) {
	//递归
	if (begin >= end) {
		return;
	}
	int mid = (begin + end) / 2;
	//[begin mid] [mid+1 end]
	_MergerSort(a, tmp, begin,mid);
	_MergerSort(a, tmp, mid+1,end);
	//合并
	int begin1 = begin;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = end;
	int index = begin;
	while (begin1 <= end1 && begin2 <= end2) {
		//放小
		if (a[begin1] < a[begin2]) {
			tmp[index] = a[begin1];
			index++;
			begin1++;
		}
		else {
			tmp[index] = a[begin2];
			index++;
			begin2++;
		}
	}
	//放剩余的元素
	while (begin1 <= end1) {
		tmp[index] = a[begin1];
		index++;
		begin1++;
	}
	while (begin2 <= end2) {
		tmp[index] = a[begin2];
		index++;
		begin2++;
	}
	//CV到a数组对应位置对应长度上
	memcpy(a + begin, tmp + begin, sizeof(HPDataTroy) * (end - begin + 1));
}
//归并排序
void MergerSort(HPDataTroy* a, int n) {
	HPDataTroy* tmp = (HPDataTroy*)malloc(sizeof(HPDataTroy) * n);
	if (tmp == NULL) {
		perror("malloc fail");
		exit(-1);
	}
	//递归子函数
	_MergerSort(a,tmp,0,n-1);
}

 递归排序思想在于先递归到单个元素,然后两两合并成有序存储到辅助空间内(tmp),之后再将已经合并的序列看成一个,和下一个继续合并,从逐个两两合并到四四合并以此类推即可。

9.2:非递归

//归并排序-非递归
void MergerSortNOR(HPDataTroy* a, int n) {
	assert(a);
	assert(n > 0);
	HPDataTroy* tmp = (HPDataTroy*)malloc(sizeof(HPDataTroy));
	if (tmp == NULL) {
		perror("malloc fail");
		exit(-1);
	}
	int gap = 1;
	while (gap < n) {
		for (int i = 0; i < n; i = i + 2 * gap) {
			int begin1 = i;
			int end1 = i + gap - 1;
			int begin2 = i + gap;
			int end2 = i + gap * 2 - 1;
			int index = i;
			//[begin1,end1][begin2,end2]
			//修正防止不越界
			if (end1 >= n) {
				end1 = n - 1;
			}
			if (begin2 >= n) {
				break;
			}
			if (end2 >= n) {
				end2 = n - 1;
			}
			//合并
			while (begin1 <= end1 && begin2 <= end2) {
				//放小
				if (a[begin1] < a[begin2]) {
					tmp[index] = a[begin1];
					index++;
					begin1++;
				}
				else {
					tmp[index] = a[begin2];
					index++;
					begin2++;
				}
			}
			//放剩余的元素
			while (begin1 <= end1) {
				tmp[index] = a[begin1];
				index++;
				begin1++;
			}
			while (begin2 <= end2) {
				tmp[index] = a[begin2];
				index++;
				begin2++;
			}
			//CV到a数组对应位置对应长度上
			memcpy(a + i, tmp + i, sizeof(HPDataTroy) * (end2-i+1));
		}
		gap = gap * 2;
	}
}

递归改非递归借助循环,控制好边界即可,大致思想和递归类似,非递归是递归的倒序。

最后,如有不足,请各位大佬指正!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值