八大排序算法代码及过程解析

八大排序

在这里插入图片描述
将所需要使用到的交换函数,放置在最前面

交换函数
void Swap(int* array, int i, int j) {
	int temp = array[i];
	array[i] = array[j];
	array[j] = temp;
}

一:插入排序

1. 直接插入排序

在已经有序的数据中给待插入的数据找到一个合适的位置
排序开始之前:假设第一个数据是有序的
排序的过程:将待插入的数据从后向前依次和有序的数据进行比较

在这里插入图片描述
代码段


/*
	时间复杂度:最坏O(n^2) 平均O(n^2)  最好O(n)
	空间复杂度:O(1)
	稳定性:稳定
	数据敏感:敏感
*/

void insertSort(int* array, int n) {
	//进行遍历
	for (int i = 0; i < n - 1; i++) {
		int end = i;
		//有序序列的最后一个位置
		int key = array[end + 1];
		//待插入得一个数据
		while (end >= 0 && array[end] > key) {
			//找到第一个小于等于key的位置
			array[end + 1] = array[end];
			//将当前数据向后移动
			--end;
		}
		array[end + 1] = key;
		//将key数据存放在当前位置
	}
}

2. 希尔排序

先选定一个整数,把待排序的文件中所有记录分成多个组,所有距离为gap的记为一组,并对每一组内的数据进行排序,然后重复进行,直到gap达到1时,所有记录的数据都排好序了。
希尔排序:进行多轮的预排序,最后再执行普通的插入排序
预排序:组内元素进行插入排序
在这里插入图片描述
代码段:

/*
时间复杂度:最坏O(n^1.3) 平均O(n^1.3)  最好O(n)
空间复杂度:O(1)
稳定性:不稳定  ---> 分组时相同值的元素不一定可以分到同一组,预排序是可能导致相对位置发生变化
数据敏感:敏感
*/

void shellSort(int* array, int n) {
	int gap = n;
	//步长
	while (gap > 1) {
		gap = gap / 3 + 1;
		//步长缩减 通过不同的gap来进行逻辑分组
		//在组内进行插入排序 不同组的元素交替进行排序

		for (int i = 0; i < n - gap; i++) {
			//一轮插入排序
			int end = i;
			//排序的最后一个位置
			int key = array[end + gap];
			//同分组之中第一个要插入的数据
			while (end >= 0 && array[end] > key) {
				//找到第一个小于等于的数据
				array[end + gap] = array[end];
				//将数据向后瞬移
				end -= gap;
				//end重新赋值
			}
			array[end + gap] = key;
			//将所需要插入的数据插入到新的位置
		}
	}
}

二:选择排序

1. 两种选择排序

每一次从待排序的数据元素之中选择出最小(最大)的一个元素,存放在序列的起始位置,知道全部待排序的数据排列完毕。
在这里插入图片描述
将所取出的最大的元素,放置在最末尾位置:
在这里插入图片描述
代码段:

/*
时间复杂度:最坏O(n^2) 平均O(n^2)  最好O(n^2)
空间复杂度:O(1)
稳定性:稳定
数据敏感:不敏感
*/

void selectSort(int* array, int n)
{
	for (int i = 0; i < n; ++i)
	{
		//start:未排序数据的最左边
		int start = i;
		//min: 最小值的位置
		int min = start;
		//从未排序的数据中找最小值
		for (int j = start + 1; j < n; ++j)
		{
			if (array[j] < array[min]) {
				min = j;
				//将j赋值给min位置
			}

		}
		Swap(array, start, min);
	}
}

  • 第二种选择排序
    在这里插入图片描述
/*
时间复杂度:最坏O(n^2) 平均O(n^2)  最好O(n^2)
空间复杂度:O(1)
稳定性:稳定
数据敏感:不敏感
*/


//第二种选择排序,从两边开始进行选择排序
void selectSort(int* array, int n) {
	int begin = 0;
	//最左边的位置
	int end = n - 1;
	//最右边的位置
	while (begin < end) {
		//左边小于右边
		int min = begin, max = begin;
		//初始化min和max位置全部指向最开始
		for (int i = begin + 1; i <= end; i++) {
			if (array[i] >= array[max]) {
				//找到最大的位置
				max = i;
			}
			if (array[i] <array[min]) {
				//找到最小的位置
				min = i;
			}
		}
		Swap(array, begin, min);
		//将最小的位置和最左边的数据交换
		if (max == begin) {
			//若最大的等于左边的数据时,则表示数据发生了变化
			max = min;
			//将min位置赋值给max
		}
		Swap(array, end, max);
		//进行最大值的交换
		begin++;
		end--;
	}
}

2. 堆排序

利用堆积树这种数据结构来实现的一种算法,需要注意的时升序建大堆,降序建小堆。
在这里插入图片描述
在这里插入图片描述

/*
时间复杂度:O(nlogn)
空间复杂度:O(1)
稳定性:不稳定  --> 调整的过程中相对位置可能会发生变化
数据敏感:不敏感
*/
//建大堆 升序排序
void ShiftDown(int* array, int n, int parent) {
	int child = 2 * parent + 1;
	//寻找孩子位置
	while (child < n) {
		if (child + 1 < n && array[child + 1] > array[child]) {
			//判断在满足条件的情况下,左右孩子两者的较大值
			child++;
			//若右边孩子比左边孩子大,则将child位置加1
		}
		if (array[child] > array[parent]) {
			//若孩子比父亲的值大,则交换父亲和孩子的位置
			Swap(array, child, parent);
			parent = child;
			//交换后重新将孩子位置赋予给父亲
			child = 2 * parent + 1;
			//重新计算新的孩子位置
		}
		else {
			break;
			//如果孩子值小于父亲,则跳出
		}
	}
}

void SortHeap(int* array, int n) {
	//建堆的过程
	for (int i = (n - 2) / 2; i >= 0; i--) {
		ShiftDown(array, n, i);
		//从下最后一个父节点开始进行建堆
	}
	//循环删除
	while (n) {
		Swap(array, 0, n - 1);
		n--;
		ShiftDown(array, n, 0);
	}
}

三:交换排序

1. 冒泡排序

相邻元素之间进行比较,大的开始向后移动
在这里插入图片描述

/*
时间复杂度:最坏O(n^2) 平均O(n^2)  最好O(n)
空间复杂度:O(1)
稳定性:稳定
数据敏感:敏感
*/

void bubbleSort(int* array, int n) {
	while (n) {
		//循环n次 
		int flag = 1;
		//设置标识符,若标识符改变,则意味着此数组已经是有序序列
		int end = n;
		//最后的位置
		for (int i = 1; i < end; i++) {
			if (array[i - 1] > array[i]) {
				//若前一项大于后一项,则进行交换
				Swap(array, i, i - 1);
				//之后将标识符置零
				flag = 0;
			}
		}
		if (flag) {
			break;
		}
		n--;
	}
}
  • C语言课本上所书写的冒泡
/*
时间复杂度:最坏O(n^2) 平均O(n^2)  最好O(n^2)
空间复杂度:O(1)
稳定性:稳定
数据敏感:敏感
*/

void BubbleSort(int* array,int n) {
	for (int i = 0; i < n - 1; i++) {
		for (int j = i + 1; j < n; j++) {
			if (array[i] > array[j]) {
				Swap(array, i, j);
			}
		}
		printf("%d ", array[i]);
	}
	system("pause");
}

2. 快速排序

2.1 hora划分法

  1. 从带划分的区间中选取一个基准值
  2. 从待划分区间的末尾开始向前查找第一个小于基准值的数据
  3. 从带划分区间的开头开始向后查找第一个大于基准值的数据
  4. 交换2,3两步所找出的数据,循环执行2,3,4步
  5. 查找到相遇的位置时,则把基准值和相遇位置的数据进行交换,完成一轮划分
    在这里插入图片描述
    在这里插入图片描述
    对于hora法来说,先从前往后找第一个大的是否可行?
    在这里插入图片描述
    在这里插入图片描述
    代码段:
//Hore分割法
int PartionHora(int* array, int begin, int end) {
	//选择基准值
	int key = array[begin];
	int start = begin;
	//开始划分
	while (begin < end) {
		//从后向前,找第一个小于key的值
		while (begin < end && array[end] >= key) {
			end--;
		}
		//从前向后,找第一个大于key的值
		while (begin < end && array[begin] <= key) {
			begin++;
		}
		//交换找到的两者值得位置
		Swap(array, begin, end);
	}
	//最后将基准值和相遇位置得值进行交换
	Swap(array, start, begin);
	//返回基准值交换后的位置
	return begin;
}

2.2 挖坑法

  1. 挖取基准值,一般情况下,第一个坑位基准值的位置
  2. 从后向前找第一个小于基准值的数据,去填上上一次的坑
  3. 从前向后找第一个大于基准值的数据,去填上上一次的坑
  4. 循环执行2,3两步
  5. 找到相遇的位置,用基准值进行填坑
    在这里插入图片描述
    代码段:
//挖坑分割法
int PartionHole(int* array, int begin, int end) {
	//基准值取出来,空出这个坑
	int key = array[begin];
	//开始划分
	while (begin < end) {
		//从后找第一个小于key的位置
		while (begin < end && array[end] >= key) {
			end--;
		}
		//将找到的此位置值填到基准值的坑里,空出一个新的坑
		array[begin] = array[end];
		//从前向后找到第一个大于key的位置
		while (begin < end && array[begin] >= key) {
			begin++;
		}
		//将找到的位置的值取出,填到之前的坑之中,又空出另一个新坑
		array[end] = array[begin];
	}
	//直到相遇之后,将最早的基准值放在最后空出来的坑之中
	array[begin] = key;
	//返回基准值此时的位置
	return begin;
}

2.3 前后指针法

前面的指针和后面的指针中间是否有大于基准值的数据:
如果前后指针连续,则表示中间没有大于基准值的数据
前后指针若不连续,则表示中间有大于基准值的数据
大数据和新发现的小数据进行交换,最终实现基准值的左边都是小于基准值的,右边都是大于基准值的。
在这里插入图片描述
代码段:

//前后指针分割法
int PartionHand(int* array, int begin, int end) {
	//最后一个小于基准值的位置
	int last = begin;
	//新发现的下一个小于基准值的位置
	int next = last + 1;
	//基准值
	int key = array[begin];
	//开始划分
	while (next <= end) {
		//若next所指的数据小于基准值,但last和next之间还存在其他数据
		//则代表两者之间还包含着大于基准值的数据存在,进行交换
		//大的数据向后移动,小的数据向前移动
		if (array[next] < key && ++last != next) {
			Swap(array, last, next);
		}
		next++;
	}
	//最后当next走完之后,将last的位置和最开始的基准值位置进行交换
	Swap(array, begin, last);
	return last;
	//返回基准值的位置
}

快速排序代码

void QuichSort(int* array, int begin, int end) {
	if (begin >= end) {
		//若begin和end之间没有其他元素,则表示两者是有序的
		return;
	}
	int keyPos = PartionHora(array, begin, end);
	//以此基准值,来划分当前区域

	QuichSort(array, begin, keyPos - 1);
	//递归法划分左边子区域
	QuichSort(array, keyPos + 1, end);
	//递归法划分右边子区域

}

3. 优化版快速排序

因为对于有序的数列,再次进行快速排序的话,会产生一个深度较大的递归,容易造成栈溢出,因此需要对其进行优化。
几个数中取中间值作为基准值,三数取中法:可以把最坏的情况变成最理想的情况,均衡划分;
对于小区间的优化,也能够减少底层递归调用的次数
在这里插入图片描述在这里插入图片描述

//三数取中法则
int GetMid(int* array, int begin, int end) {
	int mid = begin + (end - begin) / 2;
	//选择begin,和end的中间位置
	if (array[begin] < array[mid]) {
		//若起始位置的值小于中间位置
		if (array[mid] < array[end]) {
			//且中间位置的值小于尾部位置的值
			return mid;
			//则返回中间的值
		}
		else {
			if (array[begin] > array[end]) {
				//否则,若起始位置的值也大于尾部位置的值
				return begin;
				//则返回开始位置
			}
			else {
				return end;
				//否则返回尾部位置
			}
		}
	}
	else {
		//若中间位置的值不大于起始位置的值
		if (array[mid] > array[end]) {
			//开始位置小于尾部位置的值
			return mid;
			//则返回中间位置
		}
		else {
			if (array[begin] < array[end]) {
				//在中间位置不大于起始位置,且开始位置不小于尾部位置的值
				return begin;
				//则返回开始位置
			}
			else {
				return end;
				//否则返回尾部位置
			}
		}
	}
}


//hora法
int PartionOne(int* array, int begin, int end) {
	int mid = GetMid(array, begin, end);
	//三数取中 找到mid值
	Swap(array, begin, mid);
	//进行交换

	//确定基准值
	int key = array[begin];
	//开始位置
	int start = begin;


	while (begin < end) {
		//从后向前,找第一个小于基准值的位置
		while (begin < end && array[end] >= key) {
			end--;
		}
		//从前向后,找第一个大于基准值的位置
		while (begin < end && array[begin] <= key) {
			begin++;
		}
		//两者进行交换
		Swap(array, begin, end);
	}
	//最后将相遇的位置和初始位置进行交换
	Swap(array, start, begin);

	//返回基准值位置
	return begin;
}


//挖坑法
int PartionTwo(int* array, int begin, int end) {
	int mid = GetMid(array, begin, end);
	//三数取中 找到mid值
	Swap(array, begin, mid);
	//进行交换

	//确定基准值
	int key = array[begin];
	while (begin < end) {
		//从后向前找第一个小于基准值的位置
		while (begin < end && array[end] >= key) {
			end--;
		}
		//将此位置的值,填入一开始的基准值坑之中
		array[begin] = array[end];
		//从前向后找第一个大于基准值的位置
		while (begin < end && array[begin] <= key) {
			begin++;
		}
		//将此位置的值填入之前找到的小于基准值的位置
		array[end] = array[begin];
	}
	//最后将key的值填入相遇的位置
	array[begin] = key;
	//返回基准值位置
	return begin;
}


//前后指针法
int PartionThree(int* array, int begin, int end) {
	int mid = GetMid(array, begin, end);
	//三数取中 找到mid值
	Swap(array, begin, mid);
	//进行交换

	//最后一个小于基准值的位置
	int prev = begin;
	//下一个小于基准值的位置
	int cur = prev + 1;
	//基准值
	int key = array[begin];
	while (cur <= end) {
		//若下一个基准值位置和最后一个基准值位置之间不连续
		if (array[cur] < key && ++prev != cur) {
			//则进行交换
			Swap(array, prev, cur);
		}
		cur++;
	}
	//交换最后基准值和基准值的位置
	Swap(array, begin, prev);
	return prev;
}


/*
时间复杂度:最坏:O(n^2)-->不会出现   最好:O(nlogn)  平均:O(nlogn)
空间复杂度:O(logn)  函数调用栈,  极端情况: O(n)-->不会出现
稳定性:不稳定
数据敏感:敏感
*/

void SortQucik(int* array, int begin, int end) {
	if (begin >= end) {
		//下h只剩下最后一个元素时,则直接返回
		return;
	}
	int keyPos = PartionOne(array, begin, end);
	//寻找第一个基准值
	SortQucik(array, begin, keyPos);
	//左边分
	SortQucik(array, keyPos+1,end);
	//右边分
}

4. 非递归版快速排序

4.1 用栈实现快速排序

在这里插入图片描述
在这里插入图片描述
代码段:

void SortQuickStack(int* array, int n) {
	//创建栈
	Stack st;
	//初始化栈
	StackInit(&st, 10);

	//入栈操作
	if (n > 1) {
		//先入最后
		StackPush(&st, n - 1);
		//再入开始
		StackPush(&st, 0);
	}
	
	while (StackEmpty(&st) != 1) {
		//获取栈顶元素,则是后入的
		int begin = StackTop(&st);
		//删除栈顶元素
		StackPop(&st);
		//再次获取栈顶元素
		int end = StackTop(&st);
		//删除栈顶元素
		StackPop(&st);

		//返回的基准值位置
		int keyPos = PartionThree(array, begin, end);

		if (keyPos + 1 < end) {
			//后半部若还有其他未排序元素
			//入栈
			StackPush(&st, end);
			StackPush(&st, keyPos+1);
		}
		if (begin < keyPos - 1) {
			//前半部分若还有其他未排序元素
			//入栈
			StackPush(&st, keyPos - 1);
			StackPush(&st, begin);
		}
	}
}

4.2 用队列实现快速排序

在这里插入图片描述
对于队列,则是先将队列之中的区间进行划分结束之后,再划分之后进入队列之中的区间,顾名思义则是一层一层去进行划分。
代码段:

void SortQuickQueue(int* array, int n) {
	//创建队列
	Queue q;
	//队列初始化
	QueueInit(&q);

	if (n > 1) {
		//入队操作
		QueuePush(&q, 0);
		QueuePush(&q, n - 1);
	}
	while (QueueEmpty(&q) != 1) {
		//获取队头元素
		int begin = QueueFront(&q);
		//删除队头元素
		QueuePop(&q);
		//获取队头元素
		int end = QueueFront(&q);
		//删除对头元素
		QueuePop(&q);

		//找到基准值位置
		int keyPos = PartionThree(array, begin, end);
		if (begin < keyPos - 1) {
			//左半部分
			QueuePush(&q, begin);
			QueuePush(&q, keyPos-1);
		}
		if (keyPos + 1 < end) {
			//右半部分
			QueuePush(&q, keyPos+1);
			QueuePush(&q, end);
		}
	}
}

四:归并排序

1. 归并排序

合并有序的子序列,使其变成一个更大的有序的组序列。
归并合并的前提时:子序列本身必须是有序的(有序的子序列只包含一个元素的子序列本身有序)
合并过程:相邻的有序子序列进行相互合并
不能够进行原地合并,需要重新申请辅助空间,否则会造成覆盖元素。
在这里插入图片描述

1.1 递归版归并排序

//合并函数 前提是需要知道两个有序子序列的区间 [begin ,mid ][mid+1 end]
void Merge(int* array, int begin, int mid, int end, int* tmp) {
	int begin1 = begin, end1 = mid, begin2 = mid + 1, end2 = end;
	int idx = begin;

	//两个有序序列进行排序
	while (begin1 <= end1 && begin2 <= end2) {
		if (array[begin1] <= array[begin2]) {
			//将两个序列之中较小的值放入到tmp之中
			tmp[idx++] = array[begin1++];
		}
		else {
			tmp[idx++] = array[begin2++];
		}
	}
	//将剩余的数据直接拷贝到当前的空间之中
	if (begin1 <= end1) {
		memcpy(tmp + idx, array + begin1, sizeof(int) * (end1 - begin1 + 1));
	}
	if (begin2 <= end2) {
		memcpy(tmp + idx, array + begin2, sizeof(int) * (end2 - begin2 + 1));
	}
	//之后将tem之中的数据拷贝到原始空间之中
	memcpy(array + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}


void SortMergeR(int* array, int begin, int end, int* tmp) {
	if (begin >= end) {
		//若Begin和end之间没有任何数据,则直接返回
		return;
	}
	//查找中间位置
	int mid = begin + (end - begin) / 2;

	//先进行子区间的排序
	SortMergeR(array, begin, mid, tmp);
	SortMergeR(array, mid + 1, end, tmp);

	//最后合并有序的子区间
	Merge(array, begin, mid, end, tmp);
}

1.2 非递归版归并排序

在这里插入图片描述

//合并函数 前提是需要知道两个有序子序列的区间 [begin ,mid ][mid+1 end]
void Merge(int* array, int begin, int mid, int end, int* tmp) {
	int begin1 = begin, end1 = mid, begin2 = mid + 1, end2 = end;
	int idx = begin;

	//两个有序序列进行排序
	while (begin1 <= end1 && begin2 <= end2) {
		if (array[begin1] <= array[begin2]) {
			//将两个序列之中较小的值放入到tmp之中
			tmp[idx++] = array[begin1++];
		}
		else {
			tmp[idx++] = array[begin2++];
		}
	}
	//将剩余的数据直接拷贝到当前的空间之中
	if (begin1 <= end1) {
		memcpy(tmp + idx, array + begin1, sizeof(int) * (end1 - begin1 + 1));
	}
	if (begin2 <= end2) {
		memcpy(tmp + idx, array + begin2, sizeof(int) * (end2 - begin2 + 1));
	}
	//之后将tem之中的数据拷贝到原始空间之中
	memcpy(array + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

void SortMergeNoR(int* array, int n) {
	//申请新的空间
	int* tmp = (int*)malloc(sizeof(int) * n);
	//设置跨度
	int k = 1;

	//开始多轮的合并
	while (k < n) {
		//一轮合并
		for (int i = 0; i < n; i += 2 * k) {
			int begin = i;
			int mid = i + k - 1;

			//若中间值大于最大位置则继续进行
			if (mid >= n - 1) {
				continue;
			}
			int end = i + 2 * k - 1;
			
			//若末尾值大于最大位置时,则将末尾值置到最后一个位置
			if (end >= n) {
				end = n - 1;
			}
			//合并
			Merge(array, begin, mid, end, tmp);
		}
		//跨度扩大2倍
		k *= 2;
	}
}

归并排序代码

/*
时间复杂度:O(nlogn)
空间复杂度:O(n)
稳定:稳定
数据敏感:不敏感
*/
void SortMerge(int* array, int n) {
	int* tmp = (int*)malloc(sizeof(int) * n);
	SortMergeR(array, 0, n - 1, tmp);
	free(tmp);
}

0. 外排序思想

由归并排序讲解的 外排序:

在这里插入图片描述

五:非比较排序

1. 计数排序

计数排序又被称之为鸽巢原理,是对哈希直接定址法的变形应用

  1. 统计相同元素出现的次数根据统计的结果将序列回收到原来的序列之中

两种表示方式:
在这里插入图片描述
在这里插入图片描述
代码段:

/*
时间复杂度:O(max(n,范围))
空间复杂度:O(范围)
稳定:一般教材认为是稳定的(欠妥)
数据敏感:不敏感
计数排序: 只适合小范围数据, 如果范围大,空间复杂度较高
*/

void SortCount(int* array, int n) {
	//统计范围
	int min = array[0], max = array[0];
	//找到最大值和最小值
	for (int i = 1; i < n; i++) {
		if (array[i] > max) {
			max = array[i];
		}
		if (array[i] < min) {
			min = array[i];
		}
	}
	//确定准确的范围
	int range = max - min + 1;
	//开放辅助空间,进行计数
	int* countArr = (int*)malloc(sizeof(int)*range);
	//将申请的辅助空间全部初始化为零
	memset(countArr, 0, sizeof(int) * range);
	//开始统计数据出现的个数
	for (int i = 0; i < n; i++) {
		//出现一次则加一次
		countArr[array[i] - min]++;
	}
	//设置中间量
	int idx = 0;
	//恢复数据,遍历计数数组
	for (int i = 0; i < range; i++) {
		while (countArr[i]--) {
			array[idx++] = i + min;
		}
	}
	//释放辅助空间
	free(countArr);
}

各种排序的总结树状图
在这里插入图片描述
在这里插入图片描述
温故而知新,帮助自己,也是帮助别人,有则改之无则加勉(欢迎讨论)!

猜你喜欢

转载自blog.csdn.net/Luckily0818/article/details/106084729