手撕七大排序

文章目录

  • 1.排序的概念及其运用
    • 1.1排序的概念
    • 1.2排序运用
  • 2.常见的排序算法
    • 2.1插入排序
      • 2.1.1直接插入排序
      • 2.1.2希尔排序
    • 2.2 选择排序
      • 2.2.1选择排序
      • 2.2.2堆排序
    • 2.3交换排序
      • 2.3.1冒泡排序
      • 2.3.2快速排序(whoare)
      • 2.3.3快速排序 (挖坑法)
      • 2.3.4快速排序优化
      • 2.3.5快速排序前后指针法
      • 2.3.6快速排序(递归)
      • 2.3.7快速排序(非递归)
    • 2.4 归并排序
      • 2.4.1归并排序(递归)
      • 2.4.2归并排序(非递归实现)外拷贝
      • 2.4.3归并排序(非递归实现)内拷贝
      • 2.4.4内外拷贝区别
      • 2.5非比较排序
  • 3.排序算法复杂度及稳定性分析
    • 3.1直接插入排序
    • 3.2希尔排序
    • 3.3选择排序
    • 3.4堆排序
    • 3.5冒泡排序
    • 3.6快速排序
    • 3.7归并排序
    • 3.7排序算法复杂度及稳定性总结

1.排序的概念及其运用

1.1排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中r[i]=r[j],r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排
序算法是稳定的;否则称为不稳定的。
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

1.2排序运用

手撕七大排序_第1张图片

2.常见的排序算法

手撕七大排序_第2张图片

2.1插入排序

2.1.1直接插入排序

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移
手撕七大排序_第3张图片

void InsertSort(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			else
				break;
		}
		a[end + 1]=tmp;
	}
}

2.1.2希尔排序

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

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		int i = 0;
		for (i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end-=gap;
				}
				else
					break;
			}
			a[end + gap] = tmp;
		}
	}
}

2.2 选择排序

基本思想
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的
数据元素排完 。

2.2.1选择排序

直接选择排序:
在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素,若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素手撕七大排序_第5张图片

void swap(int* a,int*b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void SelectSort(int* a, int n)
{
	int begin = 0;
	int end = n - 1;
	while (begin < end)
	{
		int mini = begin;
		int maxi = begin;
		int i = 0;
		for (i = begin + 1; i <= end; i++)
		{
			if (a[i] < a[mini])
			{
				mini = i;
			}
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}
		swap(&a[mini], &a[begin]);
		if (maxi == begin)//这是处理第一个元素就是最大值的情况,
		如果第一个元素是最大值,那么就会被第一次交换换走,所以判断一下
		{
			maxi = mini;
		}
		swap(&a[maxi], &a[end]);
		begin++;
		end--;
	}
}

2.2.2堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
手撕七大排序_第6张图片

void AdjustDown(int* a, int n, int parent)
{
	int child = 2 * parent + 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 = 2 * parent + 1;
		}
		else
			break;
	}
}
void Heapsort(int* a, int n)
{
	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--;
	}
}

2.3交换排序

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

2.3.1冒泡排序

手撕七大排序_第7张图片

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

2.3.2快速排序(whoare)

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

手撕七大排序_第8张图片

void PartSort1(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int left = begin;
	int right = end;
	int keyi = left;
	while (left < right)
	{
		while (left < right&&a[right] >= a[keyi])
		{
			right--;
		}
		while(left

2.3.3快速排序 (挖坑法)

void PartSort2(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int left = begin;
	int right = end;
	int key = a[left];
	int hole = left;
	while (left < right)
	{
		while (left < right && a[right] >= key)
		{
			right--;
		}
		a[hole] = a[right];
		hole = right;
		while (left < right && a[left] <= key)
		{
			left++;
		}
		a[hole] = a[left];
		hole = left;
     }
	a[hole] = key;
	PartSort2(a, begin, hole - 1);
	PartSort2(a, hole + 1, end);
}

2.3.4快速排序优化

三数取中法,规避了第一个元素为最大值导致第一次排序无效的情况

// 三数取中
// begin  mid  end
int GetMidIndex(int* a, int begin, int end)
{
	int mid = (begin + end) / 2;
	if (a[begin] < a[mid])
	{
		if (a[mid] < a[end])
		{
			return mid;
		}
		else if (a[begin] > a[end])
		{
			return begin;
		}
		else
		{
			return end;
		}
	}
	else // a[begin] > a[mid]
	{
		if (a[mid] > a[end])
		{
			return mid;
		}
		else if (a[begin] < a[end])
		{
			return begin;
		}
		else
		{
			return end;
		}
	}
}
//放进排序开头
int mid = GetMidIndex(a, begin, end);
swap(&a[begin], &a[mid]);

2.3.5快速排序前后指针法

int PartSort3(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int keyi = begin;
	int cur = begin+1;
	int prev = begin;

	while (cur <= end)//闭区间
	{
		if (a[cur] <= a[keyi] && ++prev != cur)
		{
			swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	swap(&a[prev], &a[keyi]);
	keyi = prev;
	/*PartSort3(a,begin, keyi - 1);
	PartSort3(a, keyi+1, end);*/
	return keyi;
}

2.3.6快速排序(递归)

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}

	if ((end - begin + 1) < 15)
	{
		// 小区间用直接插入替代,减少递归调用次数
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int keyi = PartSort3(a, begin, end);

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

2.3.7快速排序(非递归)

int PartSort3(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int keyi = begin;
	int cur = begin+1;
	int prev = begin;
	//错误
	/*while (cur= a[keyi])
		{
			cur++;
		}
		swap(&a[++prev], &a[cur]);
		cur++;
	}*/
	while (cur <= end)//闭区间
	{
		if (a[cur] <= a[keyi] && ++prev != cur)
		{
			swap(&a[prev], &a[cur]);
		}
		cur++;//无论怎样cur都会++,所以我们将cur放在外面
	}
	swap(&a[prev], &a[keyi]);
	keyi = prev;
	/*PartSort3(a,begin, keyi - 1);
	PartSort3(a, keyi+1, end);*/
	return keyi;
}
void QuickSortNonR(int* a, int begin, int end)
{
	ST st;
	StackInit(&st);
	StackPush(&st, end);
	StackPush(&st, begin);
	while (!StackEmpty(&st))
	{
		int left = StackTop(&st);
		StackPop(&st);
		int right = StackTop(&st);
		StackPop(&st);
		int keyi = PartSort3(a, left, right);
		if (left < keyi - 1)
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, left);
		}
		if (right > keyi + 1)
		{
			StackPush(&st, right);
			StackPush(&st, keyi+1);
		}
	}
	StackDestroy(&st);
}

2.4 归并排序

手撕七大排序_第9张图片
基本思想
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
手撕七大排序_第10张图片

2.4.1归并排序(递归)

void _MergeSort(int* a, int begin, int end, int*tmp)
{
	if (begin >= end)
	{
		return;
	}
	int mid = (begin + end) / 2;
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid+1, end, tmp);
	int begin1 = begin;
	int end1 = mid;
	int begin2 = mid + 1;
	int 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 MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
	}
	_MergeSort(a, 0, n - 1, tmp);
	free(tmp);
	tmp = NULL;
}

2.4.2归并排序(非递归实现)外拷贝

void MergeSortNonR1(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	int range = 1;
	while (range < n)
	{
		
		for (int i = 0; i < n; i +=2 * range)
		{
			int begin1 = i;
			int end1 = begin1 + range - 1;
			int begin2 = begin1 + range;
			int end2= begin1 + 2*range - 1;
			int j = i;
			if (end1 >= n)
			{
				end1 = n - 1;
				begin2 = n;
				end2 = n - 1;
			}
			else if (begin2 >= n)
			{
				begin2 = n;
				end2 = n - 1;
			}
			else if(end2 >= n)
			{
				end2 = n - 1;
			}
			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) * (n));
		range *= 2;
	}

	free(tmp);
	tmp = NULL;
}

2.4.3归并排序(非递归实现)内拷贝

void MergeSortNonR2(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
	}
	int range = 1;
	while (range < n)
	{

		for (int i = 0; i < n; i += 2 * range)
		{
			int begin1 = i;
			int end1 = begin1 + range - 1;
			int begin2 = begin1 + range;
			int end2 = begin1 + 2 * range - 1;
			int j = i;
			if (end1 >= n)
			{
				break;
			}
			else if (begin2 >= n)
			{
				break;
			}
			else if (end2 >= n)
			{
				break;
			}
			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 + i, tmp + i, sizeof(int) * (end2 - i + 1));

		}
		
		range *= 2;
	}

	free(tmp);
	tmp = NULL;
}

2.4.4内外拷贝区别

外拷贝
我们修正空间边界,所以可以直接外拷贝
手撕七大排序_第11张图片

            if (end1 >= n)
			{
				end1 = n - 1;
				begin2 = n;
				end2 = n - 1;
			}
			else if (begin2 >= n)
			{
				begin2 = n;
				end2 = n - 1;
			}
			else if(end2 >= n)
			{
				end2 = n - 1;
			}

内拷贝

           if (end1 >= n)
			{
				break;
			}
			else if (begin2 >= n)
			{
				break;
			}
			else if (end2 >= n)
			{
				break;
			}

为什么不能外拷贝?

手撕七大排序_第12张图片

2.5非比较排序

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

  1. 统计相同元素出现次数
  2. 根据统计的结果将序列回收到原来的序列中
    手撕七大排序_第13张图片
void countsort(int* a, int n)
{
	int i = 0;
	int min = a[0];
	int max = a[0];
	for (i = 0; i < n; i++)
	{
		if (a[i] < min)
		{
			min = a[i];
		}
		if (a[i] > max)
		{
			max = a[i];
		}
	}
	int range = max - min + 1;
	int* tmp = (int*)calloc(range,sizeof(int));
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	i = 0;
	//统计次数
	for (i = 0; i < n; i++)
	{
		tmp[a[i] - min]++;
	}
	int j = 0;
	//排序
	for (int k = 0; k < range; k++)
	{
		while (tmp[k]--)
		{
			a[j++] = k + min;
		}
	}
	free(tmp);

}

计数排序的特性总结:

  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
  2. 时间复杂度:O(MAX(N,范围))
  3. 空间复杂度:O(范围)

3.排序算法复杂度及稳定性分析

排序算法稳定的意义
对四个同学(A,B,C,D)的成绩进行排序,假设他们的成绩依次为90,92,86,86
稳定排序算法输出 C D A B
不稳定排序算法输出 D C A B

3.1直接插入排序

直接插入排序,显然是稳定的,第一轮排序结果为C A B D,接下来D与B比较,B向后移动,同理A,然后排在A前面,输出C D A B

3.2希尔排序

对5个同学(A,B,C,D,E)的成绩进行排序,假设他们的成绩依次为90,86,92,86 , 86
希尔排序,是不稳定的,首先希尔排序是需要分组的A C E为一组,B D为一组,第一轮排序结果为 E B A D C,这时BDE的相对位置已经打乱了,再进行直接插入排序不会改变稳定性,所以不稳定

3.3选择排序

对四个同学(A,B,C,D)的成绩进行排序,假设他们的成绩依次为86,92,86,80
选择排序是不稳定的,第一次排序输出 D C A B, AC的相对位置就改变了

3.4堆排序

手撕七大排序_第14张图片
交换92与86输出,就改变了原来的相对位置所以也是不稳定的

3.5冒泡排序

冒泡排序是稳定的,我们只需要设置相等不交换就可以了

3.6快速排序

给定一个序列(6 3 3 7 8 9 ),最后6与第二个3交换,破环了稳定性,所以也是不稳定的

3.7归并排序

归并排序时稳定的,他的排序思想就是把序列拆开再尾插,我们只需要修改尾插判定条件为相等也为尾插就解决了这个问题

3.7排序算法复杂度及稳定性总结

手撕七大排序_第15张图片
手撕七大排序_第16张图片

你可能感兴趣的:(数据结构初阶,排序算法,算法,数据结构)