初阶数据结构:八大排序算法

初阶数据结构:八大排序算法_第1张图片

直接插入排序

单趟排序:将x插入[0,end]有序区间

一个数组a为:
在这里插入图片描述
如果插入一个3
初阶数据结构:八大排序算法_第2张图片

如果插入一个1
初阶数据结构:八大排序算法_第3张图片

	int end;
	int x;
	while (end >= 0)
	{
     
		if (a[end] > x)
		{
     
			a[end + 1] = a[end];
			end--;
		}
		else
		{
     
			break;
		}
		a[end + 1] = x;
	}

整个数组排序

void InsertSort(int* a, int n)
{
     
	assert(a);

	for (int i = 0; i < n - 1; ++i)
	{
     
		int end = i;
		int x = a[end + 1];

		while (end >= 0)
		{
     
			if (a[end] > x)
			{
     
				a[end + 1] = a[end];
				end--;
			}
			else
			{
     
				break;
			}
		}
		a[end + 1] = x;
	}
}

初阶数据结构:八大排序算法_第4张图片

希尔排序–在直接插入排序的思想上优化

1、分组预排序 – 使数组接近有序
2、从而直接插入排序 – O(N)

一个数组为:
在这里插入图片描述
按gap(gap>1)分组
初阶数据结构:八大排序算法_第5张图片

对这gap组进行插入排序
初阶数据结构:八大排序算法_第6张图片

初阶数据结构:八大排序算法_第7张图片

排蓝色这一组中的一趟

初阶数据结构:八大排序算法_第8张图片

 int gap = 3;
	int end = 0;
	int x = a[end + gap];
	while (end >= 0)
	{
     
		if (a[end] > x)
		{
     
			a[end + gap] = a[end];
			end--;
		}
		else
		{
     
			break;
		}
		a[end + gap] = x;
	}

排完蓝色这一组

在这里插入图片描述

for (int i = 0; i < n - gap; i += gap)
	{
     
		int end = i;
		int x = a[end + gap];
		while (end >= 0)
		{
     
			if (a[end] > x)
			{
     
				a[end + gap] = a[end];
				end--;
			}
			else
			{
     
				break;
			}
			a[end + gap] = x;
		}

把蓝色、红色、紫色3组都排完,间距为gap多组预排就实现了。

多组分排

int gap = 3;

	for (int j = 0; j < gap; j++)
	{
     
		for (int i = j; i < n - gap; i += gap)
		{
     
			int end = i;
			int x = a[end + gap];
			while (end >= 0)
			{
     
				if (a[end] > x)
				{
     
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
     
					break;
				}
				a[end + gap] = x;
			}

		}
	}

多组并排

初阶数据结构:八大排序算法_第9张图片

for (int i = 0; i < n - gap; ++i)
			{
     
				int end = i;
				int x = a[end + gap];
				while (end >= 0)
				{
     
					if (a[end] > x)
					{
     
						a[end + gap] = a[end];
						end -= gap;
					}
					else
					{
     
						break;
					}
					a[end + gap] = x;
				}

时间复杂度:
最好:O(N)
最坏:F(N,gap) = (1+2+3+…+N/gap)*gap
gap越大,预排越快,预排后越不接近有序
gap越小,预排越慢,预排后越接近有序

平均:O(N^1.3)

//多次预排序(gap>1)+直接插入(gap == 1)
//O(N)=(logN) * N
 
	int gap = n;
	while (gap > 1)
	{
     
		/*gap = gap / 2;*/  //(log2(N))* N
		gap = gap / 3 + 1;  //(log3(N))* N

		//多组一锅炖
		//这个循环每次很接近O(N)
			for (int i = 0; i < n - gap; ++i)
			{
     
				int end = i;
				int x = a[end + gap];
				while (end >= 0)
				{
     
					if (a[end] > x)
					{
     
						a[end + gap] = a[end];
						end -= gap;
					}
					else
					{
     
						break;
					}
				}
					a[end + gap] = x;
			}
	  }

选择排序

在[begin,end]中选出最大值的下标maxi和最小值的下标mini!

初阶数据结构:八大排序算法_第10张图片

初阶数据结构:八大排序算法_第11张图片

堆排序

//建大堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
     
		AdjustDown(a, n, i);
	}

初阶数据结构:八大排序算法_第12张图片

void HeapSort(int* a, int n)
{
     
	//建大堆
	//O(N)
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
     
		AdjustDown(a, n, i);
	}

	//排序
	//O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
     
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;

	}
}

冒泡排序

单趟

void BubbleSort(int* a, int n)
{
     
	//1:
	for (int i = 1; i < n; i++)
	{
     
		//升序:前一个数比后一个数大,则交换
		if (a[i - 1] > a[i])
		{
     
			Swap(&a[i-1], &a[i]);
		}
	}

	//2:
	/*for (int i = 0; i < n - 1; i++)
	{
		if (a[i] < a[i + 1])
		{
			Swap(&a[i], &a[i + 1]);
		}
	}*/
}

整趟

void BubbleSort(int* a, int n)
{
     

	//1:
	/* for (int j = 0; j < n; j++)
	{
		for (int i = 1; i < n-j; i++)
		{
			//升序:前一个数比后一个数大,则交换
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
			}
		}
	}*/

	//2:
	int end = n;
	while (end > 0)
	{
     
		for (int i = 1; i < end; i++)
		{
     
			//升序:前一个数比后一个数大,则交换
			if (a[i - 1] > a[i])
			{
     
				Swap(&a[i - 1], &a[i]);
			}
		}
		end--;
	}	 
}

初阶数据结构:八大排序算法_第13张图片
当出现这种情况,部分有序时,交换后变成都有序的了,不用再继续比较了,所以做进一步的优化:

//时间复杂度:O(N^2)
//最好:O(N)
void HeapSort(int* a, int n)
{
     
	int end = n;
	while (end > 0)
	{
     
		int enchange = 0;
		for (int i = 1; i < end; i++)
		{
     
			//升序:前一个数比后一个数大,则交换
			if (a[i - 1] > a[i])
			{
     
				enchange = 1;
				Swap(&a[i - 1], &a[i]);
			}
		}
		end--;

		if (enchange == 0)
		{
     
			break;
		}
	}
}

初阶数据结构:八大排序算法_第14张图片
此时会跳出循环,冒泡排序完成。

直接插入排序、选择排序、冒泡排序对比

初阶数据结构:八大排序算法_第15张图片
选择最差,因为无论什么场景下都是O(N^2)

直接插入和冒泡排序,最坏都是O(N^2),最好都是O(N):
已经有序数组排序,一样好
对于接近有序数组,直接插入更好
综合而言,直接插入更好
初阶数据结构:八大排序算法_第16张图片

快速排序

1、hoare版本

单趟排序

一般选最左边/最右边的值做key
单趟排序以后的目标:左边的值比key小,右边的值比key大。

选最左边的值做key,右边先走 左右相遇时比key大
初阶数据结构:八大排序算法_第17张图片
初阶数据结构:八大排序算法_第18张图片

分析单趟的最后一趟:
如果是左边遇右边,右边是比key小的数,所以相遇时比key小
如果是右边遇到左边,因为左边是刚刚被右边交换过的的,所以相遇时也是比key小

选最右边的值做key,左边先走,左右相遇时比key大

初阶数据结构:八大排序算法_第19张图片

初阶数据结构:八大排序算法_第20张图片

分析最后一趟:

当左边遇到右边时,右边是刚刚被左边交换过的,所以相遇时比key大。

初阶数据结构:八大排序算法_第21张图片

当右边遇到左边时,左边是比key大的数,所以相遇时比key大。
初阶数据结构:八大排序算法_第22张图片

于是可以写出如下代码:

void Partion(int* a, int left, int right)
{
     
	int keyi = left;
	while (left < right)
	{
     
		//右边先走,找小
		while (a[right] > a[keyi])
		{
     
			right--;
		}

		//左边再走,找大
		while (a[left] < a[keyi])
		{
     
			left++;
		}

		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[keyi]);
 }

但是出现如下情况时
特殊场景1:
初阶数据结构:八大排序算法_第23张图片
特殊场景2:
初阶数据结构:八大排序算法_第24张图片

所以改善为:

void Partion(int* a, int left, int right)
{
     
	int keyi = left;
	while (left < right)
	{
     
		//右边先走,找小
		while (left<right && a[right] >= a[keyi])
			right--;

		//左边再走,找大
		while (left<right && a[left] <= a[keyi])
			left++;

		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[keyi]);
 }

整趟排序

单趟排序后,比key小的都在左边,比key大的都在右边

初阶数据结构:八大排序算法_第25张图片

如果左子区间有序,右子区间有序,整体就有序了

初阶数据结构:八大排序算法_第26张图片

void QuickSort(int* a, int left, int right)
{
     
	if (left >= right)
		return;

	int keyi = Partion(a, left, right);
	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

初阶数据结构:八大排序算法_第27张图片
画出递归展开图如下:

初阶数据结构:八大排序算法_第28张图片

快排的缺陷

假设每次取的key都是中位数
初阶数据结构:八大排序算法_第29张图片
最坏情况,数组有序
初阶数据结构:八大排序算法_第30张图片

递归程序缺陷:
递归程度太深,会导致栈溢出。
在快排的最坏情况下,很可能会栈溢出。

初阶数据结构:八大排序算法_第31张图片
那么如何解决快排面对有序选key问题?
1、随机选key
2、三数取中,在最左边,中间,最右边三个数中取不是最大,也不是最小的左key。

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

int Partion1(int* a, int left, int right)
{
     
	int mini = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mini]);
	int keyi = left;
	while (left < right)
	{
     
		//右边先走,找小
		while (left<right && a[right] >= a[keyi])
			right--;

		//左边再走,找大
		while (left<right && a[left] <= a[keyi])
			left++;

		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[keyi]);

	return left;
 }

挖坑法

单趟

初阶数据结构:八大排序算法_第32张图片

右边找小
初阶数据结构:八大排序算法_第33张图片
找到后放入坑中,形成新坑。
初阶数据结构:八大排序算法_第34张图片
依次下去

相遇后把key放入坑中
初阶数据结构:八大排序算法_第35张图片

int Partion2(int* a, int left, int right)
{
     
	int mini = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mini]);

	int key = a[left];
	int pivot = left;

	while (left < right)
	{
     
		//右边找小,放到左边的坑里面
		while (left < right && a[right] >= key)
		{
     
			right--;
		}
		a[pivot] = a[right];
		pivot = right;

		//左边找大,放到右边的坑里面
		while (left < right && a[left] <= key)
		{
     
			left++;
		}
		a[pivot] = a[left];
		pivot = left;
	}
	a[pivot] = key;
	return pivot;
}

前后指针版

初阶数据结构:八大排序算法_第36张图片
初阶数据结构:八大排序算法_第37张图片

int Partion3(int* a, int left, int right)
{
     
	int prev = left;
	int keyi = left;
	int cur = prev + 1;
	while (cur <= right)
	{
     
		if (a[cur] < a[keyi] && ++prev != cur)
		{
     
			Swap(&a[cur], &a[prev]);
		}
		cur++;

	}
	Swap(&a[prev], &a[keyi]);
	return prev;
}

初阶数据结构:八大排序算法_第38张图片

快排的非递归

初阶数据结构:八大排序算法_第39张图片

void QuickSortNonR(int* a, int left, int right)
{
     
	ST st;
	StackInit(&st);
	StackPush(&st, a[left]);
	StackPush(&st, a[right]);

	while (!StackEmpty(&st))
	{
     
		//栈是后入先出
		int end = StackTop(&st);
		StackPop(&st);
		int begin = StackTop(&st);
		StackPop(&st);

		int keyi = Partion3(&a, begin, end);
		
		//先入右边,则先排左边
		if (keyi + 1 < end)
		{
     
			StackPush(&st, keyi + 1);
			StackPush(&st, end);
		}

		if (begin > keyi - 1)
		{
     
			StackPush(&st, begin);
			StackPush(&st, keyi - 1);
		}

	}
}

归并排序

归并递归

void _MergeSort(int* a, int left, int right, int* tmp)
{
     
	if (left >= right)
		return;

	int mid = (left + right) / 2;
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);

	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int i = left;
	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 (int j = left; j<= right; j++)
	{
     
		a[j] = tmp[j];
	}
}


void MergeSort(int* a, int n)
{
     
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
     
		printf("malloc fail\n");
		exit(-1);
	}

	_MergeSort(a, 0, n - 1, tmp);
}

初阶数据结构:八大排序算法_第40张图片

归并的非递归

在这里插入图片描述
初阶数据结构:八大排序算法_第41张图片
如果数组是如下这种:
在这里插入图片描述

则有三种特殊情况:

情况一:[begin2,end2]不存在
初阶数据结构:八大排序算法_第42张图片

初阶数据结构:八大排序算法_第43张图片

情况二:end1越界,[begin2, end2]不存在

初阶数据结构:八大排序算法_第44张图片

初阶数据结构:八大排序算法_第45张图片
情况三:end2越界
初阶数据结构:八大排序算法_第46张图片
如何解决?

//核心思想:end1, begin2, end2都有可能越界
			//end1越界, 或者begin2越界都不用归并
			if (end1 >= n || begin2 >= n)
			{
     
				break;
			}

			//end2越界,需要归并,修正end2
			if (end2 >= n)
			{
     
				end2 = n - 1;
			}

归并的非递归全部代码:

void MergeSortNonR(int* a, int n)
{
     
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
     
		printf("malloc fail\n");
		exit(-1);
	}

	int gap = 1;
	while (gap < n)
	{
     
		for (int i = 0; i < n; i += gap * 2)
		{
     
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

			//核心思想:end1, begin2, end2都有可能越界
			//end1越界, 或者begin2越界都不用归并
			if (end1 >= n || begin2 >= n)
			{
     
				break;
			}

			//end2越界,需要归并,修正end2
			if (end2 >= n)
			{
     
				end2 = n - 1;
			}

			int index = begin1;
			while (begin1 <= end1 && begin2 <= end2)
			{
     
				if (a[begin1] < a[begin2])
				{
     
					tmp[index++] = a[begin1++];
				}
				else
				{
     
					tmp[index++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
     
				tmp[index++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
     
				tmp[index++] = a[begin2++];
			}

			//把小区间的数据拷贝回原数组
			for (int j = i; j <= end2; j++)
			{
     
				a[j] = tmp[j];
			}

		}
		gap *= 2;
	}

	free(tmp);
	tmp = NULL;
}

计数排序

在这里插入图片描述
用一个数组count统计出现的次数
初阶数据结构:八大排序算法_第47张图片
比如a[0]的值为0出现的次数。

再根据统计的次数给原数组排序
在这里插入图片描述

但是如果数组是这样的呢?
初阶数据结构:八大排序算法_第48张图片

则没有必要开1500个字节的空间,只需要开max-min+1个字节的空间给count数组即可统计图上数据的次数,此时计算的是映射位置:x-min。
比如count[x-min],就代表图上数据x出现的次数。

void CountSort(int* a, int n)
{
     
	int max = a[0], min = a[0];
	for (int i = 1; i < n; i++)
	{
     
		if (a[i] > max)
		{
     
			max = a[i];
		}
		if (a[i] < min)
		{
     
			min = a[i];
		}
	}
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	memset(count, 0, sizeof(int) * range);
	if (count == NULL)
	{
     
		printf("malloc fail\n");
		exit(-1);
	}

	//统计次数
	for (int i = 0; i < n; i++)
	{
     
		count[a[i] - min]++;
	}

	//根据次数排序
	int j = 0;
	for (int i = 0; i < n; i++)
	{
     
		while (count[i]--)
		{
     
			a[j++] = i + min;
		}
	}
}

习题

1

初阶数据结构:八大排序算法_第49张图片
初阶数据结构:八大排序算法_第50张图片

2

初阶数据结构:八大排序算法_第51张图片
初阶数据结构:八大排序算法_第52张图片

3

初阶数据结构:八大排序算法_第53张图片
初阶数据结构:八大排序算法_第54张图片

你可能感兴趣的:(数据结构,C语言,c语言,算法,链表)