八大排序附详细注释

1.直接插入排序

void Swap(int* p, int* q)
{
	int tmp = *p;
	*p = *q;
	*q = tmp;
}
void InsertSort(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		//在排下标为i的数的时候,闭区间[0, i-1]已经有序了
		for (int end = i - 1; end >= 0; end--)
		{
			//我们将arr[i]和前面有序的数依次比较,如果前面的数比arr[i]大,就交换该数和arr[i],并记得i--;
			//如果前面的数不大于arr[i],arr[i]已经到了应该到的位置
			if (arr[end+1] < arr[end])
			{
				Swap(&arr[end+1], &arr[end]);
			}
			else
			{
				break;
			}
		}
	}

	//时间复杂度,O(N^2);
	//空间复杂度,O(1);
	//稳定性:稳定。
}

2.希尔排序

void ShellSort(int* arr, int n)
{
	//希尔排序是对直接插入排序的优化。分为预排序和直接插入排序两步
	//  我们先将原来的数据中间隔为gap的分为一组,一共gap组。
	//  然后对同组数据进行直接插入排序,这样做可以使较大的数尽量放在后面,较小的数尽量放在前面
	//  gap越大,较大的数就能更快的到后面,但是gap越大也同样意味着分成了更多的组,我们要对更多的组进行直接插入排序
	//  所以gap并不是一个确定的数,我们要将gap逐渐的缩小,最后缩小到1的时候在进行最后一次直接插入排序
	//  下面我们把gap值置成5,每次缩小1/3为例写一下代码
	int gap = 5;
	while (gap > 1)
	{
		//for (int i = 0; i < gap; i++)
		//{
		//	//比如gap为5的时候,我们要将原始数据分为5组,那就要对5组数据进行直接插入排序
		//	for (int j = i; j <= n - gap + i; j += gap)
		//	{
		//		for (int end = j - gap; end >= 0; end -= gap)
		//		{
		//			if (arr[end + gap] < arr[end])
		//			{
		//				Swap(&arr[end + gap], &arr[end]);
		//			}
		//			else
		//			{
		//				break;
		//			}
		//		}
		//	}
		//}

		//以上三层for循环虽然代码没有问题,但是可以改写成两层for循环,因为第一次for循环是为了将gap组数据都进行插入排序
		//我们可以这样想,我们虽然把数据分成gap组,但是我们可以每组的元素排一个,之前是把一组全拍完才排下一组,现在我们雨露均沾
		//我们遍历一次原始数据,就可以把所有组都进行旋转排序
		for (int i = 0; i < n; i++)
		{
			for (int end = i - gap; end >= 0; end -= gap)
			{
				if (arr[end + gap] < arr[end])
				{
					Swap(&arr[end], &arr[end + gap]);
				}
				else
				{
					break;
				}
			}
		}
		gap = gap/3 + 1;//这个加1是为了保证gap跳出循环的时候值为1,如果不加,比如gap=6,除三两次变成0了
	}
	InsertSort(arr, n);


	//时间复杂度:平均O(N^1.3) 在O(N^logN)和O(N^2)之间
	//空间复杂度:O(1);
	//稳定性:不稳定,相同的数可能被分到不同的gap组
}

3.选择排序

void SelectSort(int* arr, int n)
{
	//选择排序,是指遍历一次n个元素,找出最大的与最后位置的元素进行交换。
	//然后遍历剩余n-1个元素,再找到最大的与倒数第二个位置的元素进行交换,如此循环往复下去
	for (int i = n-1; i >= 0; i--)
	{
		for (int j = 0; j < i; j++)
		{
			if (arr[j] > arr[i])
			{
				Swap(&arr[j], &arr[i]);
			}
		}
	}
	//时间复杂度:O(N^2)
	//空间复杂度:O(1)
	//稳定性:不稳定,简单举一个例子  
	//100   80  80 ,第一个100和最后一个80交换,然后就排好升序了,那么曾经在后面的80跑到了前面
}

4.堆排序

void AdjustUp(int* arr, int child)
{
	//以小堆为例
	int father = (child - 1) / 2;
	while (father >= 0)
	{
		if (arr[father] > arr[child])
		{
			Swap(&arr[father], &arr[child]);
			child = father;
			father = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
void AdjustDown(int* arr, int end,int start)
{
	//end是指arr中最后一个元素下标,start是从下标为start的位置开始向下调整
	//以小堆为例
	int father = start;
	int child = 2 * start + 1;
	while (child <= end)
	{
		if (child + 1 <= end && arr[child + 1] < arr[child])
		{
			child += 1;
		}
		if (arr[father] > arr[child])
		{
			Swap(&arr[father], &arr[child]);
			father = child;
			child = 2 * father + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(int* arr, int n)
{
	//先将这n个元素建堆,排升序,建大堆,排降序,建小堆。
	// 我们今天以降序为例,建小堆
	1.向上调整建堆  O(N*logN)
	//for (int i = 0; i < n; i++)
	//{
	//	AdjustUp(arr, i);
	//}
	//2.向下调整建堆  O(N)
	for (int i = (n - 2) / 2; i >= 0; i--)
	{
		AdjustDown(arr, n-1, i);
	}

	//建好堆然后降序 堆顶是最小的元素,与最后一个元素交换位置,然后从堆顶开始向下调整
	for (int i = n-1; i >= 0; i--)
	{
		Swap(&arr[0], &arr[i]);
		AdjustDown(arr, i - 1, 0); 
	}//O(N*logN)

	//所以整个堆排序的时间复杂度是O(N*logN)
	//空间复杂度:O(1)
	//稳定性:不稳定

}

5.冒泡排序

void BubbleSort(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		//每一趟把左闭右开区间[0, n-i)中最大的数交换到最后,随着i的增大,交换的区间逐渐缩小。
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				Swap(&arr[j], &arr[j + 1]);
			}
		}
	}
	//时间复杂度O(N^2);
	//空间复杂度:O(1);
	//稳定性:稳定
}

6.快速排序

//方法1:hoare版  发明快排这个人写的
//思路:选取最左面的值为key(你想选右面的值做key也ok),让right先往左走找到比key小的停下来,然后left往右走找到比key大的停下来
//交换arr[left]和arr[right],当left和right相遇,交换key和arr[left],此时key找到了他该在的位置,在递归调用快排使左右区间也有序
//int PartSort1(int* arr, int left, int right)
//{
//	//选左面的值作为key值
//	int keyi = left;
//	while (left < right)
//	{
//		//在不越界的前提下
//		//让右面先走,找到比key小的停下来
//		while (left < right && arr[right] >= arr[keyi])
//		{
//			right--;
//		}
//		//然后左面走,找到比key大的停下来
//		while (left < right && arr[left] <= arr[keyi])
//		{
//			left++;
//		}
//		Swap(&arr[left], &arr[right]);
//	}
//	Swap(&arr[keyi], &arr[left]);
//	return left;
//}
//void QuickSort(int* arr, int start, int end)
//{
//	if (start >= end)
//	{
//		return;
//	}
//	int keyi = PartSort1(arr, start, end);
//	QuickSort(arr, start, keyi - 1);
//	QuickSort(arr, keyi + 1, end);
//}


//方法2:挖坑法
//思路:每一趟排序先把最左面元素作为key并保存起来,此时left可以被覆盖,left此时就是‘坑’
//然后right向左找到比key小的停下来,把这个值放进坑里,即arr[left] = arr[right]
//然后left向右走找到比key大的停下来,把这个值放进坑里,即arr[right] = arr[left]
//知道left和right相遇,相遇处就是key该放的位置
//再递归调用快排使左右区间也有序
//int PartSort2(int* arr, int left, int right)
//{
//	int key = arr[left];
//	while (left < right)
//	{
//		while (left< right && arr[right] >= key)
//		{
//			right--;
//		}
//		arr[left] = arr[right];
//		while (left < right && arr[left] <= key)
//		{
//			left++;
//		}
//		arr[right] = arr[left];
//	}
//	arr[left] = key;
//	return left;
//	
//}
//void QuickSort(int* arr, int start, int end)
//{
//	if (start >= end)
//	{
//		return;
//	}
//	int keyi = PartSort2(arr, start, end);
//	QuickSort(arr, start, keyi - 1);
//	QuickSort(arr, keyi + 1, end);
//}

//方法3:前后指针法
//思路:将arr[left]作为key,prev为最左面元素下标,cur = prev+1
//cur往右走,找到比key小的停下来,让prev++,然后交换arr[prev]和arr[cur],然后cur继续往后走
//当cur越界,prev所在的位置就是key应该在的位置,交换key和arr[prev]即可,并将prev返回
//在递归调用快排,使左右区间也有序
//int PartSort3(int* arr, int left, int right)
//{
//	int key = arr[left];
//	int prev = left;
//	int cur = left + 1;
//	while (cur <= right)
//	{
//		if (arr[cur] < key)
//		{
//			prev++;
//			if (prev < cur)
//			{
//				Swap(&arr[prev], &arr[cur]);
//			}
//		}
//		cur++;
//	}
//	Swap(&arr[prev], &arr[left]);
//	return prev;
//}
//void QuickSort(int* arr, int start, int end)
//{
//	if (start >= end)
//	{
//		return;
//	}
//	int keyi = PartSort3(arr, start, end);
//	QuickSort(arr, start, keyi - 1);
//	QuickSort(arr, keyi + 1, end);
//}


//快排的优化
// 我们知道,以上三种递归思路的快排在函数调用时会像二叉树一样创建大量栈帧,尤其是如果本来就是有序序列,二叉树就退化成了单支树
// 此时递归的深度很深,效率很低。所以我们可以在选择key值得时候,选出left,right,middle下标对应的中间大的那个值作为key值,避免这种情况
// 此外,当要排序的区间已经很小的时候,没必要使用递归建立那么多的栈帧,我们可以在数据量小于一定值的时候直接使用插入排序
// 下面以前后指针法为例做一下优化
//方法3:前后指针法
//思路:将arr[left]作为key,prev为最左面元素下标,cur = prev+1
//cur往右走,找到比key小的停下来,让prev++,然后交换arr[prev]和arr[cur],然后cur继续往后走
//当cur越界,prev所在的位置就是key应该在的位置,交换key和arr[prev]即可,并将prev返回
//在递归调用快排,使左右区间也有序
int GetMid(int* arr, int left, int right)
{
	//注意返回值是中间大的那个数对应的下标
	int mid = (left + right) >> 1;
	if (arr[left] > arr[right])
	{
		if (arr[left] < arr[mid])
		{
			return left;
		}
		else if (arr[mid] > arr[right])
		{
			return mid;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (arr[right] < arr[mid])
		{
			return right;
		}
		else if (arr[mid] > arr[left])
		{
			return mid;
		}
		else
		{
			return left;
		}
	}
}
int PartSort3(int* arr, int left, int right)
{
	int mid = GetMid(arr, left, right);
	Swap(&arr[left], &arr[mid]);
	int key = arr[left];
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (arr[cur] < key)
		{
			prev++;
			if (prev < cur)
			{
				Swap(&arr[prev], &arr[cur]);
			}
		}
		cur++;
	}
	Swap(&arr[prev], &arr[left]);
	return prev;
}
//void QuickSort(int* arr, int start, int end)
//{
//	if (start >= end)
//	{
//		return;
//	}
//	if (end - start + 1 <= 10)
//	{
//		//待排序序列元素个数不超过10个,直接插入排序
//		InsertSort(arr + start, end - start + 1);
//	}
//	int keyi = PartSort3(arr, start, end);
//	QuickSort(arr, start, keyi - 1);
//	QuickSort(arr, keyi + 1, end);
//}

//快排的时间复杂度O(N*logN)
//空间复杂度:O(logN) -- O(N)
//稳定性:不稳定

//非递归实现快排,借助栈
void QuickSortNonR(int* arr, int start, int end)
{
	stack<int> st;
	st.push(start);
	st.push(end);
	while (!st.empty())
	{
		int right = st.top();
		st.pop();
		int left = st.top();
		st.pop();
		int keyi = PartSort3(arr, left, right);
		if (left < keyi - 1)
		{
			st.push(left);
			st.push(keyi - 1);
		}
		if (keyi + 1 < right)
		{
			st.push(keyi + 1);
			st.push(right);
		}
	}
}

7.归并排序

void _MergeSort(int* arr, int left, int right, int* tmp)
{
	if (left >= right)
	{
		return;
	}
	int mid = (left + right) / 2;
	//[left, mid]  [mid+1, right]
	//先让左右两个区间有序
	_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 i = left;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] < arr[begin2])
		{
			tmp[i++] = arr[begin1++];
		}
		else
			tmp[i++] = arr[begin2++];
		
	}
	//走到这肯定是左面序列或右面序列到头了,把剩下的元素走完即可
	while (begin1 <= end1)
	{
		tmp[i++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = arr[begin2++];
	}
	memcpy(arr+left, tmp+left, sizeof(int) * (right - left + 1));
}
void MergeSort(int* arr, int n)
{
	//归并排序,当元素个数大于1个,就先分成两部分,当左右两部分都有序了,在合并两个有序序列4
	//归并排序需要开辟空间,然后再拷贝回原空间
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(1);
	}
	_MergeSort(arr, 0, n - 1, tmp);
	free(tmp);
	tmp = NULL;
	
}






void MergeSortNonR(int* arr, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(1);
	}
	int gap = 1;//指每次归并[begin1, end1][begin2, end2]每个区间的元素个数
	while (gap < n)
	{
		int j = 0;
		for (int i = 0; i < n; i+=2*gap)
		{
			int begin1 = i;
			int end1 = begin1+gap-1;
			int begin2 = end1+1;
			int end2 = begin2 + gap-1;
			//begin1就是i,i始终小于n,所以begin1肯定不会越界
			//end1 = begin1+gap-1,这个值当begin1较大时(即i较大)且gap较大时,就可能越界
			//至于begin2,end2肯定是大于end1的,所以他们两个更可能越界
			//针对越界情况我们要对下标进行一下修正
			if (end1 >= n)
			{
				//第一个区间不全,这个由于上次合并成这个区间的两个区间中的第二个区间不全,所以这个不全的区间已经有序了break即可
				break;
			}
			if (begin2 >= n)
			{
				//第一个区间全但是第二个区间不存在   其实判断条件写成==也可以,因为走到这里end1肯定小于n,而begin2=end1+1,只有end1是n-1的时候,begin2才会等于n
				break;//将被合并的区间是两个有序区间,现在第二个区间不存在,第一个区间本就有序,直接break
			}
			if (end2 >= n && begin2 < n)
			{
				//第二个区间存在但不全
				end2 = n - 1;
			}
			
			printf("归并[%d, %d][%d, %d]\n", begin1, end1, begin2, end2);
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (arr[begin1] < arr[begin2])
				{
					tmp[j++] = arr[begin1++];
				}
				else
				{
					tmp[j++] = arr[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = arr[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = arr[begin2++];
			}
		}
		memcpy(arr, tmp, sizeof(int) * j);
		gap *= 2;
	}
	free(tmp);
}
//时间复杂度:O(N*logN)
//空间复杂度:O(N);
//稳定性:稳定

8.计数排序

//计数排序
//思路:再开一个新的数组初始化为0,遍历原数组,统计原数组的数字出现多少次,并在新数组对应下标位置++
void CountSort(int* arr, int n)
{
	int min = arr[0];
	int max = arr[0];
	//先找出原数组最大的数和最小的数
	for (int i = 1; i < n; i++)
	{
		if (arr[i] < min)
		{
			min = arr[i];
		}
		if (arr[i] > max)
		{
			max = arr[i];
		}
	}

	//相对映射
	int range = max - min + 1;
	int* tmp = (int*)malloc(sizeof(int) * range);
	if (NULL == tmp)
	{
		printf("malloc fail\n");
		exit(2);
	}
	memset(tmp, 0, sizeof(int) * range);
	for (int i = 0; i < n; i++)
	{
		tmp[arr[i] - min]++;
	}
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (tmp[i]--)
		{
			arr[j++] = i + min;
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

逃跑的机械工

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

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

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

打赏作者

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

抵扣说明:

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

余额充值