各种排序(排到头秃)

大噶下午好!我又来了,今天给大家介绍一下排序,是所有的排序哦。

稳定性:将需插入的数放到合理位置,数组中其他数的顺序是否会发生变化。不变化->稳定性好,反之稳定性不好。

  • 插入排序:

时间复杂度(最坏/最好/平均情况):O(N^2)/O(N)/ O(N^2)

稳定性:稳定

概述:将需插入的数插入到有序数组中,挨个比较判断是否需要交换,因为数组已经有序了,所以每个数在进行插入时最多只需要遍历数组1次。

//插入排序
void InsertSort(int* a1, int n)
{
	//int i = 0;
	//int j = 0;
	//for (j = 1; j < n; j++)
	//{
	//	
	//	for (i = j; i > 0; i--)
	//	{
	//		//将插入的数的前一个数存储进tmp
	//		int tmp = a1[i - 1];

	//		//插入的数小于前一个数
	//		if (a1[i] < a1[i - 1])
	//		{
	//			//交换
	//			a1[i - 1] = a1[i];
	//			a1[i] = tmp;
	//		}
	//		else
	//			break;
	//	}
	//}
	int i = 0;
	for (i = 1; i < n; i++)
	{
		int end = i;
		int tmp = a1[end];
		while (end >= 1)
		{

			//判断大小,升序
			if (a1[end-1] > tmp)
			{
				a1[end] = a1[end-1];
				end--;
			}
			else
				break;
		}
		a1[end] = tmp;
	}
}

②冒泡排序:

时间复杂度(最坏/最好/平均情况):O(N^2)/O(N)/ O(N^2)

稳定性:稳定

概述:数组内成员从最左边开始两两相比,将 大/小(取决于递增/递减) 数放在后面,直至区间所有的数比较完毕,这样一次就可以确定好数组最后一个位置的数,然后重复上述步骤 (右区间下标-1 -> 因为上一次排序已经确定好最右边的数值了) 。

③选择排序:

时间复杂度(最坏/最好/平均情况):O(N^2)/O(N^2)/ O(N^2)

稳定性:不稳定

概述:选取数组内 最大/最小(取决于 递增/递减 排序) 的数 -> 遍历1变数字数组,然后放在 第1个/最后1个 位置,再次进行寻找值时,区间的范围可以缩小(因为上一次已经确定好了一个位置的数)。循环往复即可。

//选择排序
void SelectSort(int* a1, int n)
{

	int i = 0;
	//区间开始下标
	int begin = 0;

	//区间结束下标
	int end = n - 1;
	
	//全部排序
	while (begin < end)
	{
		int min = begin;
		int max = begin;
		//先排序一次
		for (i = begin+1; i < end+1; i++)
		{
			//确定最小值下标
			if (a1[i] < a1[min])
			{
				min = i;
			}

			//确定最大值下标
			if (a1[i] > a1[max])
			{
				max = i;
			}
		}
		//交换
		Swap(&a1[min], &a1[begin]);

		//判断max是否与begin相等
		if (max == begin)
		{
			max = min;
		}
		Swap(&a1[max], &a1[end]);
		begin++;
		end--;
	}
}
  • 希尔排序:

时间复杂度(最坏/最好/平均情况):O(N^2)/O(N^1.3)/ O(N*logN)~O(N^2)

稳定性:不稳定

概述:将数组分为gap组数,每组有 N/gap 数,每组数据中2个数的间隔为gap,然后将每组数据排好顺序。随后再次分组(在上一次分组的基础上组数减少,即:gap减少),然后再将每组数据排好顺序。重复以上步骤,指针gap(组数)为1排好序。

//希尔排序
void ShellSort(int* a1, int n)
{

	int gap = n;
	int i = 0;
	int j = 0;

	while (gap > 1)
	{
		//组数
		gap = (gap / 3) + 1;

		//所有组都参与进来
		for (j = 0; j < gap; j++)
		{
			//首先将每一组排好序(其中一组的排序)
			for (i = j+gap ; i < n; i += gap)
			{
				//插入的数值下标
				int end = i;
				int tmp = a1[end];
				while (end > j)
				{
					//插入的数小于前一个数就交换
					if (a1[end-gap] > tmp)
					{
						a1[end] = a1[end-gap];
						end -= gap;
					}
					else
						break;
				}
				a1[end] = tmp;
			}
		}
	}
}

⑤快速排序:

时间复杂度(最坏/最好/平均情况):O(N^2)/O(N*logN)/O(N*logN)

(1)递归:

  ①左右指针:

    概述:将待排区间最左边的数的下标标记为指针 mid、left ,将区间最右边的数的下标标记为指针 right。

(核心:数组[right] < 数组[mid]时停止移动,left则相反)

**单趟(蛋汤)排序:

我们先不考虑2个指针相遇情况: 首先,判断right指针是否符合核心,不符合就向左移动,直至符合核心,然后,判断left是否符合核心,若不符合,向右移动,直至符合核心,当2个指针都符合核心时,交换2个数,在此基础上继续循环。那么问题来了:什么时候结束呢?答:当2个指针相遇时 -> 当2个指针相遇时说明发生了2种情况:①left没有找到符合核心的一直在移动、②right没有找到符合核心的一直在移动。①当left一直在移动时,则说明right已经符合核心了(比数组[mid]小),当相遇时,则说明:right左边的值全部 < 数组[mid]数组[right]数组[left]指向的数 < 数组[mid],right右边的数 >=  数组[mid],那么2个相遇位置的数与数组[mid] 交换,就是 数组[mid] 应该在的位置。②当right一直在移动时,说明:数组[left] <= 数组[mid],当2个指针相遇时,左边的数 < 数组[mid],右边的数 > 数组[mid],相遇位置 <=  数组[mid]。那么相遇位置就是 数组[mid] 应该存放的正确位置。(这里我们有一个需要注意的地方:right一定要先走,①这样才可以保证2指针相遇时指向的值一定比 数组[mid] 小 ②可以说明本次交换开始,left还未移动/left指向的数 < 数组[mid]

      **整体排序:

        单趟排序确定好 数组[mid] 的正确位置之后,将这个位置作为区间分割点:左区间and右区间(习惯先递归左区间)。当一个区间只有1个数/left>right时,则不需要递归(return)。

  ②左右指针(挖坑):与上述核心相同,只是要先将 数组[mid] 拿出去,将left/right符合核心的数放入坑中,放入坑中后产生新的坑。

//快速排序
void QuickSort(int* a1, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}

	int left = begin;
	int right = end;
	int keyi = begin;

	while (left < right)
	{
		//让right先走,寻找比keyi小的值
		while (left < right && a1[right] >= a1[keyi])
		{
			right--;
		}

		//让left走,找比keyi大的值
		while (left < right && a1[left] <= a1[keyi])
		{
			left++;
		}
		Swap(&left, &right);
	}
	//keyi右边全部交换好,准备将keyi放入正确的位置
	Swap(&a1[left], &a1[keyi]);
	//keyi要起到分割的作用了
	keyi = left;
	
	//先走左边
	//[begin-keyi-1]  keyi  [keyi+1,end]
	QuickSort(a1, begin, keyi - 1);

	//走右边
	QuickSort(a1, keyi + 1, end);
}

  ③快慢指针:

**单趟(蛋汤)排序:

        首先我们选取待排区间第1个数下标 key为关键点,创建2个前后指针curprev(cur与prev不在同一位置起点比较好写算法),cur在key的位置,prev在后面1个位置。循环内容:开始逐步移动prev,每移动1次prev就比较1次(与数组[key]比较),若 数组[prev]  < 数组[key] ,将cur增加1(向后移动1位), 数组[cur] 数组[prev] 交换,一直循环下去直至prev超出区间范围。

        循环结束后将 数组[key] 数组[cur] 交换,并将key更改为cur作为下一次递归的分割点。

        本质上是将比数组[key]小的值推到前面,而cur在最后一定是指向小于数组[key]区间的最后1个数,这时交换数组[key]与数组[cur],可以保证数组[key]移动到正确位置。

      **整体排序:

        用上一次的 key 作为分割点进行递归,左区间and右区间,当区间内的 prev > end(区间最右边下标)时终止递归。

//⑧快速排序(前后指针法)
void QuickFBpSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	int cur = begin + 1;
	int prev = begin;
	int key = begin;

	while (cur <= end)
	{
		if (a[cur] < a[key] && ++prev != cur)
		{
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	Swap(&a[key], &a[prev]);
	key = prev;

	QuickFBpSort(a, begin, key - 1);
	QuickFBpSort(a, key+1, end);
}

(2)非递归:

        总体思路:非递归需要用到栈存储待排数组 begin end 的下标,找到关键点,然后遵循递归的排序顺序,将栈中的数据排空就说明排序完成。由于栈是后进先出,我们可以根据自己的需要来选择放入的下标。

        具体实现方法:(需要创建1个找到关键点的函数 - KeyPoint )

将待排数组的 begin 和end (下标)放进栈中,然后从栈中pop出2个数,用 KeyPoint 找到关键点,作为区分左右区间的分割点,然后将左右区间的4个下标放入栈中 ---  往复循环,直至栈中无数据

//⑨快速排序(非递归法)
void QuickSortkNotR(int* a, int begin, int end)
{
	//创建栈(后进先出)
	ST stack;
	//初始化栈
	Stack_init(&stack);

	Stack_push(&stack, begin);
	Stack_push(&stack, end);
	

	while (Stack_empty(&stack) != NULL)
	{
		//区间最右边下标
		int right = Stack_top(&stack);
		Stack_pop(&stack);

		//区间最左边下标
		int left = Stack_top(&stack);
		Stack_pop(&stack);

		//将key放到属于它的位置,然后记录key值
		int key = PartOneFBPSort(a, left, right);

		//进行下一组排序
		  //右边
		if (key + 1 < end)
		{
			Stack_push(&stack, key + 1);
			Stack_push(&stack, end);
		}
		//左边
		if (key - 1 > begin)
		{
			Stack_push(&stack, begin);
			Stack_push(&stack, key - 1);
		}
	}
	//销毁栈
	 Stack_destroy(&stack);
}
  • 归并排序:

时间复杂度(最坏/最好/平均情况):O(N*logN)/O(N*logN)/O(N*logN)

  概念:归并排序实际上是一个将有序区间组数不断缩小、区间不断扩大的排序方式。

**递归:①找到待排区间中间坐标mid作为分割点将区间分为左区间和右区间(要将mid包含进去),②随后进行递归,直到left >= right,递归终止。③进行排序

void _MargeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;
	int mid = (begin + end) / 2;

	_MargeSort(a, begin, mid, tmp);
	_MargeSort(a, mid + 1, end, tmp);

	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	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++];
	}
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));		
}

//⑩归并排序(递归)
void MargeSort(int* a, int begin, int end)
{
	int* tmp = (int*)malloc(sizeof(int) * (end - begin + 1));
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	_MargeSort(a, begin, end, tmp);

}

**非递归:思路是相同的,都是进行分组排序,利用rangeN变量来控制组内成员个数,首先我们要将rangeN变量设置为1(组内成员个数为1),然后2组相比较/排序,直到整个数组排序完毕。然后调整rangeN (以2倍的方式调整),再重复上述步骤。这个思路需要注意的是:调整好组内成员个数时,要注意会不会越界访问,若存在越界访问问题,及时调整小组的区间 -> 调整下标。

//11.①归并排序(非递归)- 修正溢出的区间下标
void MargeSortNotR(int* a, int begin, int end)
{
	int* tmp = (int*)malloc(sizeof(int) * (end - begin + 1));
	int i = 0;
	int rangeN = 1;
	while (rangeN < end + 1)
	{
		int j = 0;
		for (i = 0; i <= end; i += 2 * rangeN)
		{
			int begin1 = i, end1 = begin1 + rangeN - 1;
			int begin2 = end1 + 1, end2 = begin2 + rangeN - 1;
			//修正
			if (end1 > end)
			{
				end1 = end;
				begin2 = end;
				end2 = end - 1;
			}
			else if (begin2 > end)
			{
				begin2 = end;
				end2 = end - 1;
			}
			else if (end2 > end)
			{
				end2 = end;
			}
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
					tmp[j++] = a[begin2++];
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
		}
		memcpy(a, tmp, sizeof(int) * (end - begin + 1));
		rangeN *= 2;
	}
}

      以上就是各种排序的详情介绍了,本人已累摊。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值