0
点赞
收藏
分享

微信扫一扫

各种主流排序汇总

王传学 2022-04-14 阅读 75

1.插入排序

直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为 止,得到一个新的有序序列 。

//以排升序为例
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void InsertSort(int* a, int n)
{
	int i = 0;
	for (i = 0;i < n;i++)//遍历整个数组n个数排n次
	{
		int end = i;//end是指排到第m个数时前面已经有end个数字排好序了
		
		while (end > 0)//排第m个数时,和前面end个数字依次比较大小
		{
			if (end >= 1 && a[end - 1] > a[end])//如果前面数字大于后面数字就交换
			{
				Swap(&a[end - 1], &a[end]);
				end--;//没交换一次前面排好序的数字就少一个
			}
			else
			{
				break;
			}
		}
	}
}

2.希尔排序

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个 组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工 作。当到达=1时,所有记录在统一组内排好序。

image-20220410224931910

希尔排序是对直接插入排序的优化,当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就 会很快。这样整体而言,可以达到优化的效果。

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;//gap为步长,距离为gap的所有数分为一组,gap不断调整,直到最后gap==1,这个过程中整个数组逐渐接近有序
		for (int i = 0;i < n;i++)//遍历整个数组,我们把整个数组分成gap组,每组排一个数
		{
			int end = i;//end是我们即将要排的数的下标
			while (end > 0)
			{
				if (end >= gap && a[end - gap] > a[end])//end>=gap确保end前面有数  如果前面数字比后面大就交换
				{
					Swap(&a[end - gap], &a[end]);
					end -= gap;
				}
				else
				{
					break;
				}
			}
		}
	}
}

3.选择排序

思想:每次从待排数据中选出最小的和最大的,分别放在最左面和最右面。每次待排数数据依次减少两个

void SelectSort(int* a, int n)
{
	int left = 0;
	int right = n - 1;
	while (left < right)
	{
		for (int i = left;i <= right;i++)
		{
			if (a[i] < a[left])
			{
				Swap(&a[i], &a[left]);
			}
			if (a[i] > a[right])
			{
				Swap(&a[i], &a[right]);
			}
		}
		left++;
		right--;
	}
}

4.堆排序

数据结构–关于堆的那些事_btzxlin的博客-CSDN博客

5.冒泡排序

void BubbleSort(int* a, int n)
{
	int i = 0;
	for (i = 0;i < n;i++)
	{
		int j = 0;
		for (j = 0;j < n - i - 1;j++)
		{
			if (a[j] > a[j + 1])
			{
				Swap(&a[j], &a[j + 1]);
			}
		}
	}
}

6.快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

方法1hoare法

void QuickSort1(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int left = begin;
	int keyi = left;//取最左面的元素为基准值,keyi为左面元素下标
	int right = end;
	while (left < right)
	{
		while (left<right && a[right] >= a[keyi])//右边先找到一个小于key的数
		{
			right--;
		}
		while (left < right && a[left] <= a[keyi])//左边找到一个大于key的数
		{
			left++;
		}
		Swap(&a[left], &a[right]);//次数,左右各找到了一个大于key和小于key的数,将他们交换位置
	}
	Swap(&a[keyi], &a[left]);//while循环结束时,left和right相遇,此时相遇点左边都比key小,右边都比key大,此时将key和相遇点的值交换,这样就使key到了正确的位置
	QuickSort1(a, begin, left - 1);//递归左边区间
	QuickSort1(a, left + 1, end);//递归右面区间
    //递归完成后每一个值都到了正确的位置,排序结束
}

方法2挖坑法

void QuickSort2(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int left = begin;
	int right = end;
	int key = a[left];//将第一个元素保存再key中,这时候left位置可以看作一个坑
	while (left < right)
	{
		while (left<right && a[right]>=key)
		{
			right--;
		}
		a[left] = a[right];//右面先走,当找到第一个比key小的数,放到坑里,此时right位置变成了一个坑

		while (left < right && a[left] <= key)
		{
			left++;
		}
		a[right] = a[left];//右面走完左面走,当找到第一个比key大的数字,放到坑里,此时left又变成了坑

	}
	a[left] = key;//当while循环结束后,left和right相遇,相遇处为坑,把key放进坑中,此时实现了key的左面都比他小,右面都比他大
	QuickSort2(a, begin, left - 1);//递归key左面的区间
	QuickSort2(a, left + 1, end);//递归key右面的区间
     //递归完成后每一个值都到了正确的位置,排序结束
}

3.左右指针法

void QuickSort3(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int prev = begin;//prev是起始元素下标
	int cur = prev + 1;//cur是prev的下一个元素下标
	int keyi = prev;//keyi为选取的基准值的下标
	while (cur <= end)//cur往后走
	{
		if (a[cur] < a[keyi] && ++prev != cur)//如果遇到比key小的,prev++,交换prev和cur位置的值
		{
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	Swap(&a[prev], &a[keyi]);//当while循环结束时,prev左面都比key小,右面都比key大,交换prev和keyi位置的值,此时key被放到正确的位置
	QuickSort3(a, begin, prev - 1);//递归key左面区间
	QuickSort3(a, prev + 1, end);//递归key右面区间
}

4.快速排序优化

1.三数取中法选key

int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) >> 1;
	
	if (a[left] < a[mid])//left  mid
	{
		if (a[mid] < a[right])//left mid right
		{
			return mid;
		}
		else if (a[right] < a[left])//right left  mid 
		{
			return left;
		}
		else//left right mid
		{
			return right;
		}

	}
	else//a[mid] < a[left]
	{
		if (a[right] < a[mid])//right mid left
		{
			return mid;
		}
		else if (a[right] > a[left])//mid left right
		{
			return left;
		}
		else//mid right left
		{
			return right;
		}
	}
}

2小区间优化

当递归到小区间时,可以考虑使用插入排序,以减少递归调用的次数

5.非递归实现快排

void QuickSortNonR(int* a, int begin, int end)//借助栈来实现
{
	Stack st;
	StackInit(&st);
	StackPush(&st, begin);
	StackPush(&st, end);
	while (!StackEmpty(&st))//栈不为空就进去排
	{
		int left, right;
		right = StackTop(&st);
		StackPop(&st);
		left = StackTop(&st);
		int start = left;//利用start和over保存一下left和right最初的位置,待会left和right会变
		int over = right;
		StackPop(&st);
		int keyi = left;//取left为基准值
		while (left < right)
		{
			while (left < right && a[right] >= a[keyi])//右面先走找到第一个比key小的停下来
			{
				right--;
			}
			while (left < right && a[left] <= a[keyi])//左面后走找到第一个比key大的停下来
			{
				left++;
			}
			Swap(&a[left], &a[right]);
		}
		Swap(&a[keyi], &a[left]);//while循环结束后,left和right相遇了,相遇点左面都比key小,右面都比key大,交换相遇点和keyi位置的值  
		
		if (start < left - 1)//left此时为相遇点下标,当相遇点左面的数大于等于两个就继续插入然后排序
		{
			StackPush(&st, start);
			StackPush(&st, left-1);
		}
		if (left + 1 < over)//同理上面
		{
			StackPush(&st, left+1);
			StackPush(&st, over);
		}
		
	}
	StackDestroy(&st);
}

7.归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

7.1递归法

void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)//此时要排的数只有一个直接return
	{
		return;
	}
	int mid = (left + right) >> 1;//入股要排的数据比1多,就分两半
	
	_MergeSort(a, left, mid, tmp);//再递归调用子函数,排左半部分区间
	_MergeSort(a, mid+1, right, tmp);//递归调用子函数,排右半部分区间
	
	int begin1 = left;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = right;
	int i = left;
	int j = left;
	while (begin1 <= end1 && begin2 <= end2)//当左右两半都没有走完,就选出较小的那个数放在tmp里面
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
		
	}
    //上面循环结束后说明左右两半其中有一部分走完了,那就判断是那个走完了。如果左半部分走完了就把右半部分剩下的数依次放进tmp中。如果右半部分走完了就把左半部分剩下的数依次放进tmp
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
    //tmp里面已经是我们拍好的数组,我们要将其拷贝回原数组
	for (j = left;j <= right;j++)
	{
		a[j] = tmp[j];
	}

}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);//先开辟n个整型的空间用于一会保存数据
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	_MergeSort(a, 0, n - 1, tmp);//一会还要递归,总不能每次递归都开辟空间,所以我们调用子函数
	free(tmp);
}

7.2非递归实现

void _MergeSortNonR(int* a, int* tmp, int begin1, int end1, int begin2, int end2)
{
	
	
	int i = begin1;
	int j = begin1;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}

	}
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	for (;j <= end2;j++)
	{
		a[j] = tmp[j];
	}
}
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);//先开辟n个整型的空间用于一会保存数据
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	int gap = 1;//gap每次归并每组的个数,最开始1个数和1个数归并,然后两个数和两个数归并,然后四个和四个……
	while (gap < n)
	{
		for (int i = 0;i < n;i += 2 * gap)//i最开始为0,每次归并2*gap个数,i就向后挪动2*gap
		{
            //前面一组的开头和结尾
			int begin1 = i;
			int end1 = i + gap - 1;
            //后面一组的开头和结尾
			int begin2 = i + gap;
			int end2 = i + 2 * gap - 1;
			if (begin2 >= n)//后面那组不存在就不用继续归并了
			{
				break;
			}
			if (end2 >= n)//后面那组不完整,end2修正为n-1,防止越界
			{
				end2 = n - 1;
			}

			_MergeSortNonR(a, tmp, begin1, end1,begin2, end2);//进行归并
		}
		gap *= 2;
	}
	free(tmp);
}

8.非比较排序(计数排序)

思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:

  1. 统计相同元素出现次数
  2. 根据统计的结果将序列回收到原来的序列中
void CountSort(int* a, int n)
{
	int max = a[0];
	int min = a[0];
	for (int i = 0;i < n;i++)//先找到待排序数组最大值和最小值
	{
		if (a[i] > max)
		{
			max = a[i];
		}
		if (a[i] < min)
		{
			min = a[i];
		}
	}
	int range = max - min + 1;
	int* count = malloc(sizeof(int) * range);//利用相对映射开辟数组,count下标为0对应min,count下标为range对应max
	memset(count, 0, sizeof(int) * range);//先把开辟的数组都置为0
	for (int i = 0;i < n;i++)
	{
		count[a[i] - min]++;//遍历原数组,原数组是几,就在其对应的count数组的位置++
	}
    //到这,原数组每个数出现几次都统计在了count里
	int i = 0;
	for (int j = 0;j < range;j++)//遍历count数组
	{
		while (count[j]--)//count[j]是只原数组中(min+j)这个数出现了多少次
		{
			a[i++] = j + min;//拷贝回原数组
		}
	}
	free(count);
}

举报

相关推荐

0 条评论