排序算法 7+2

108 阅读7分钟

[本文主要参考]

天勤2022数据结构(七)排序_天勤排序算法口诀-CSDN博客

image.png

《大话数据结构》

#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 10000

typedef struct {
	int r[MAXSIZE + 1];
	int length;
}SqList;

void InsertSort(SqList *L); // 适合小数组  (简单排序中性能最好的一个) 
void HalfInsertSort(SqList *L);
void ShellSort(SqList *L); // 希尔排序不稳定   快 些儿 选 一堆朋友 

void SelectSort(SqList *L);
void HeapSort(SqList *L); 
void HeapAdjust(SqList *L, int s, int m); 

void BubbleSort(SqList *L);
void QuickSort(SqList *L); // 被认为最慢的冒泡排序的升级版   适合大数组  版本最强 
void QSort(SqList *L, int low, int high);
void QSort2(SqList *L, int low, int high);
int Partition(SqList *L, int low, int high);
int Partition1(SqList *L, int low, int high); 

void MergeSort(SqList *L); //  归并排序 和快排一样利用了二叉树,效率不低 
void MSort(int SR[], int TR1[], int s, int t);
void Merge(int SR[], int TR[], int i, int m, int n);
void MergeSort2(SqList *L); // 非递归实现的归并效果更优 
void MergePass(int SR[], int TR[], int s, int n);

void RadixSort(SqList *L); // 桶排序适合关键字较多但是关键字的组成较简单的情况   //  分配 收集  分配 收集  ...

int main(){
	// 线表的index = 0位不存储数据, 当temp用 
	SqList a = {{0, 9, 1, 5, 8, 3, 7, 4, 6, 2}, 9};

	for (int i = 1; i <= a.length; ++i) {
		printf("%d ", a.r[i]);
	}
	
	/* 排序方法 */ 
//	InsertSort(&a);
//	HalfInsertSort(&a);
//	ShellSort(&a);

//	SelectSort(&a);
//	HeapSort(&a);

//	BubbleSort(&a);
//	QuickSort(&a);

//	MergeSort(&a);	
	MergeSort2(&a);
	
	printf("\n排序后: \n");
	
	for (int i = 1; i <= a.length; ++i) {
		printf("%d ", a.r[i]);
	}
	
	return 0;
} 
void swap(SqList *L, int i, int j){
	int temp = L->r[i];
	L->r[i] = L->r[j];
	L->r[j] = temp;
} 

void InsertSort(SqList *L){
	int i,j;
	for(i=2; i<=L->length; ++i){
		L->r[0] = L->r[i];
		for(j=i-1; L->r[0] < L->r[j]; --j){
			L->r[j+1] = L->r[j];
		}
		L->r[j+1] = L->r[0];
	}
}
void HalfInsertSort(SqList *L){
	int high, low, mid, i, j;
	for(i=2; i<=L->length; ++i){
		L->r[0] = L->r[i];
		high = i-1;
		low = 1;
		while(high >= low){
			mid = (low + high) / 2;
			// 相当于找 刚好 <= L->r[0] 的index
			// 《 不看也罢 》(最好的情况下 若干有 L->[0] == L->[n], low = n, high = n+1 时, 下次循环  low刚好 == high)
			if(L->r[mid] > L->r[0]){
				high = mid-1;
			}else{
				low = mid+1;
			}
		} 
		
		for(j=i-1; j>high; --j){
			L->r[j+1] = L->r[j];
		}
		L->r[high+1] = L->r[0];
	}
}
// 问题1:怎么样分组,对每组进行插入排序的? 
// 1: 每隔delta个为一组  如第1个, 第delta+1个, 第2*delta+1个, 第3*delta个为一组  
// 问题2:为什么delta不是逐一减少,为什么不会造成顺序不对? 
// 2: 因为最后一次就是直接插入排序,其实可以不用前面的操作,直接 
// 让delta=1的进行直接插入排序, 前面的操作只是使数组基本有序来减少基本操作的次数 
void ShellSort(SqList *L){
	int i, j;
	int delta = L->length;
	
	do{
		delta = delta/3 + 1;
		for(i=delta+1; i<=L->length; ++i){
			L->r[0] = L->r[i];
			for(j=i-delta; j>0 && L->r[j]>L->r[0]; j-=delta){ // 为什么插排不用j>0? 因为插排最后一定是j=0,  L->r[j]>L->r[0] 一定不成立,但这里可能数组越位 
				L->r[j+delta] = L->r[j]; 
			}
			L->r[j+delta] = L->r[0]; 
		}		 
	}while(delta != 1);
}
void SelectSort(SqList *L){
	int i, j, min;
	
	for(i=1; i<L->length; ++i){ // 比完倒数第二个元素 数列就有序了 
		min = i;
		for(j=i+1; j<=L->length; ++j){
			if(L->r[min] > L->r[j]){
				min = j;
			}
		}
		swap(L, min, i);
	}
}
// 将待排序的数组看作是一个二叉树层序遍历的结果,调整成一个大(小)顶堆 
// 将堆的顶放到数组最后(或最前),不考虑它,然后继续构建大(小)顶堆, 重复这个过程 
void HeapSort(SqList *L){
	int i; 
	for(i=L->length/2; i>0; --i){
		// HeapAdjust只会以根、左孩、右孩为一个单元调整为大顶堆,不能一次实现整体大顶,
		// 因此要从最后一个非叶子结点不断循环调整 
		HeapAdjust(L, i, L->length);
	}
	for(i=L->length; i>1; --i){
		swap(L, 1, i);
		HeapAdjust(L, 1, i-1);
	}
} 
void HeapAdjust(SqList *L, int s, int m){
	int temp, j;
	temp = L->r[s];
	for(j=2*s; j<=m; j*=2){
		// 为什么多了一个j<m的判断? 
		// 因为 j*=2 可能一下子数组越位 
		if(j<m && L->r[j]<L->r[j+1]){
			++j;
		}
		// 找出左右结点中更大的 L->r[j],再到L->r[j]的左右子树中找更大的,不断循环 
		//  L->r[s], s 分别记录上一轮的根节点处的数据值和位置,
		// 也就是每轮循环操作下来找到的最大值 该赋值到的位置 
		if(temp >= L->r[j]) // 数组越位的情况下走到这里,由于数组默认值是0,因此必break; 
			break;
		// 注意下面这两行的意义不太一样,一个是把max放到上一轮的根节点,空出max的位置给下一次循环 
		// 一个是更新上一轮的根节点 s 为这一轮的 max的位置 j, 为下一轮循环做准备 
		// 也就是说每一次for循环都会挪一次位置并空出新位置 
		L->r[s] = L->r[j];
		s = j;
	}
	L->r[s] = temp; // 把最开始的根节点的值放到空出来的地方 
} 
// 这里直接是优化之后的冒泡算法 
void BubbleSort(SqList *L){
	int i, j, flag = 1; 
	 
	// 最外层循环只代表次数 
	for(i=1; i<L->length && flag; ++i){
		flag = 0; // 只有存在交换行为才变成1
		for(j=L->length-1; j>=i; --j){
			if(L->r[j+1] < L->r[j]){
				flag = 1;
				swap(L, j+1, j); 
			} 
		} 
	} 
}
void QuickSort(SqList *L){
//	QSort(L, 1, L->length);
	QSort2(L, 1, L->length);
}
void QSort(SqList *L, int low, int high){
	int pivot;
	if(low<high){
		pivot = Partition1(L, low, high);
		QSort(L, low, pivot-1);
		QSort(L, pivot+1, high);
	}
}
// 将选取的 pivotkey不断交换,将比它小的换到它的左边,比它大的换到它的右边
// 它也在交换中不断更改自己的位置,直到完全满足这个要求为止
// 每次调用Partition()中的pivotkey是始终不变的 
int Partition(SqList *L, int low, int high){
	int pivotkey;
	
	pivotkey = L->r[low];  
	while(low<high){
		while(low<high && L->r[high] >= pivotkey)
			--high;
		swap(L, low, high);
		while(low<high && L->r[low] <= pivotkey)
			++low;
		swap(L, low, high);
	}
	return low;
}
// 三数取中法优化 pivotkey = L->r[low]; 枢轴选取不当的问题
// 还有九数取中这里不多说 
// 另外还做了减少不必要交换方面的优化 
int Partition1(SqList *L, int low, int high){
	int pivotkey;
	
	// 保证low存储 左中右 的中间值 
	int m = low + (high - low) / 2;
	if(L->r[low] > L->r[high])
		swap(L, low, high);
	if(L->r[m] > L->r[high]) 
		swap(L, high, m);
	if(L->r[m] > L->r[low])
		swap(L, m, low);
	// 优化部分 ↑
	
	// 下面也有优化 ↓ pivotkey只在最终位置作交换 
	pivotkey = L->r[low];  
	L->r[0] = pivotkey; 
	while(low<high){
		while(low<high && L->r[high] >= pivotkey)
			--high;
		L->r[low] = L->r[high]; 
		while(low<high && L->r[low] <= pivotkey)
			++low;
		L->r[high] = L->r[low]; 
	}
	// 最后一定是 L->r[low]位置空出来了,把 pivotkey放到这 
	L->r[low] = L->r[0];  
	return low;
}
// 如果是小数组就用插入排序 
void QSort2(SqList *L, int low, int high){
	int pivot;
//	if((high-low) > 100){ // 超过一定数量用快排 
		while(low < high){
			pivot = Partition1(L, low, high);
			QSort2(L, low, pivot-1); // 仍然是递归 
			low = pivot+1; // 改成了迭代 
		}
//	}else{
//		InsertSort(L);
//	}
}
void MergeSort(SqList *L){
	MSort(L->r, L->r, 1, L->length); 
}
void MSort(int SR[], int TR1[], int s, int t){
	int m;
	int TR2[MAXSIZE+1];
	if(s==t){
		TR1[s] = SR[s];
	}else{
		m = (s+t) / 2;
		MSort(SR, TR2, s, m);
		MSort(SR, TR2, m+1, t);
		Merge(TR2, TR1, s, m, t);
	}
}
// 将有序的SR[i...m]和SR[m+1...n]归并为有序的TR[i...n] 
void Merge(int SR[], int TR[], int i, int m, int n){
	int j, k, l; // k代表要放到TR的什么位置 
	for(j=m+1, k=i; i<=m && j<=n; ++k){ // 这里的i直接用的传入的参数  i是左半个数组 j是右半个数组 
		if(SR[i] < SR[j]){
			TR[k] = SR[i++];
		}else{
			TR[k] = SR[j++];
		}
	}
	// 不论是左半数组还是右半数组剩下的都该放到数组的右边的剩下位置 
	if(i<=m){  
		for(l=0; l<=m-i; ++l){
			TR[k+l] = SR[i+l];
		}
	}
	if(j<=n){
		for(l=0; l<=n-j; ++l){
			TR[k+l] = SR[j+l];
		} 
	}
}
// 很明显看出递归实现每次都要int TR2[MAXSIZE+1]; 浪费了很多空间 
// 下面改成迭代实现  
void MergeSort2(SqList *L){
	int* TR = (int*)malloc(L->length * sizeof(int));
	int k = 1;
	while(k<L->length){
		MergePass(L->r, TR, k, L->length);
		k*=2;
		MergePass(TR, L->r, k, L->length);
		k*=2;
	}
}
void MergePass(int SR[], int TR[], int s, int n){
	int i=1;
	int j;
	while(i <= n-2*s+1){ // n-2*s+1 是要归并的最后一对数组最左侧元素的index 
		Merge(SR, TR, i, i+s-1, i+2*s-1); // i, i+s-1, i+2*s-1 分别是 左半数组的最左index, 左半数组的最右index, 右半数组的最右index (其实和递归的是一样的) 
		i = i+2*s;
	}
	if(i<n-s+1) // 剩下不到一对但是超过一个数组 ? 
		Merge(SR, TR, i, i+s-1, n);
	else // 剩下不到一个数组 ? 
		for(j=i; j<=n; ++j)
			TR[j] = SR[j];
}