chap 8排序

chap 8排序

动态演示排序网站:Comparison Sorting Visualization

8.1 插入排序

算法思想:

每次将一个待排序的记录插入到前面已经排好序的子序列中,直到所有序列插入完成。

①直接插入排序

代码:
cpp 复制代码
//直接插入排序
void InsertSort(int A[], int n){
	int i, j, temp;
	for(i = 1; i < n; i ++){
		if(A[i] < A[i - 1]){
			temp = A[i];
			for(j = i - 1; j >= 0 && A[j] > temp; j--)	A[j + 1] = A[j];
			A[j + 1] = temp;
		}
	}
}

例子

当13待排序时,从后往前依次比较,当 前数 比13大的时候(Aj > temp),前数后移一个位置( Aj + 1 = Aj )

当前数 不大于13/遍历 完 时,循环停止在当前数的后一个位置插入13

注:带哨兵版本
cpp 复制代码
//A[0]空出来用于存当前待存元素
void InsertSort(int A[], int n){
	int i, j;
	for(i = 2; i <= n; i ++){
		A[0] = A[i];
		if(A[0] < A[i - 1]){
			for(j = i - 1; A[j] > A[0]; j-- ) A[j + 1] = A[j]; //往后挪一位
			A[j + 1] = A[0];
		}
	}
}

②折半插入排序

待排序记录前面的序列已经有序,在查找元素的时候采用折半查找的方法。

与折半查找不太相同的是,为了保证排序的稳定性,当找到和待排记录相等的元素时,继续序列右半部分找。

代码:

当low > high时折半查找停止,将 low , n - 1中的元素全部右移

cpp 复制代码
void InsertSort(int A[], int n){
	int i, j, mid, low, high;
	for(int i = 2; i <= n; i ++){
		A[0] = A[i];
		if(A[i] < A[i - 1]){
			low = 1, high = i - 1; 
			while(low <= high){
				mid = (high + low)/2;
				if(A[mid] <= A[0])  low = mid + 1;//等于待排元素的时候向区间的右端查询
				else high = mid - 1;
			}
			for(j = i - 1; j > low; j --) A[j + 1] = A[j];
			A[high + 1] = A[0];
		}
	}
}

③希尔排序

算法思想:先追求表中元素部分有序,再逐渐逼近全局有序

​ 先将待排序表分割成若干形如 L i, i + d, i + 2d, i + 3d, ...这样的特殊子表,对各个子表分别进行插入排序。

再缩小增量d,重复上述过程,直至d = 1.

例子:

​ 当d = 4时,i = 1, i + 4 = 5 为一组,49和76为一组;i= 2, i + 4 = 6为一组,38和13为一组; i = 3, i + 4 = 7为一组,65和27为一组; i = 4,i + 4 = 8,76和49为一组。

​ 当d = 2时,i = 1, 3,5,7为一组; i = 2, 4, 6 , 8为一组

​ 当d = 1时,i = 1, 2, 3, 4, 5, 6, 7, 8为一组

代码:
cpp 复制代码
void ShellSort(int A[], int n){
	int d, i, j;//d循环步长; i循环各个不同的子序列; j循环子序列里面的元素,间隔步长d
	for(d = n/2; d >= 1; d /=2){
		for(i = 1 + d; i<= n; i ++){
			if(A[i] < A[i - d]){
				A[0] = A[i];//A[0]不是哨兵,暂存此刻待排元素
				for(j = i - d; j>0 && A[0] < A[j]; j -= d) A[j + d] = A[j];
				A[j + d] = A[0];
			}
		}
	}
}
稳定性:

直接插入排序稳定,希尔排序不稳定。

8.2 交换排序

①冒泡排序

算法思想:

从后往前,两两比较相邻元素,若为逆序则交换他们;当某一趟没有元素交换的时候,说明元素已经有序

代码:
cpp 复制代码
void BubbleSort(int A[], int n){
	for(int i = 0; i < n - 1; i++ ){
		bool tag = false;
		for(int j = n - 1; j > i; j--){
		if(A[j - 1] > A[j]) swap(A[j - 1], A[j]);
		tag = true;
		}
		if(tag = false) return;
	}
}

int swap(int a,int b){
	int temp = a;
	a = b;
	b = temp;
}

性能:

复杂度:

最好 最坏 平均
时间复杂度 O(n) O(n^2) O(n^2)
比较次数 n - 1 n(n-1)/2 /
交换次数 0 3*n(n - 1)/2 /

一次交换需要移动三次元素

稳定性:稳定

适用于: 链表和顺序表

②快速排序***

算法思想:

每次选择一个枢轴(一般为首元素),一趟排序划分为左右两个子表,使得左子表中的元素都小于枢轴,右子表中的元素都大于等于枢轴

代码(很重要!!!):
cpp 复制代码
void QuickSort(int A[], int low, int high){
	if(low < high){
		int pivotpos = Partition(A, low, high);
        QuickSort(A,low, pivotpos - 1);
        QuickSort(A, pivotpos + 1, high);
	}
}

int Partition(int A[], int low, int high){
	int pivot = A[low];
	while(low < high){
		while(low < high && A[high] >= pivot) high--;
		A[low] = A[high];
		
		while(low < high && A[low] <= pivot) low ++;
		A[high] = A[low];
	}
	A[low] = pivot;
	return low;
}
性能

复杂度:

最坏 最优 平均
时间复杂度 O(n^2) O(n*logn) 接近O(n * log n)
空间复杂度 O(n) O(logn) O(log n)

稳定性:不稳定

适用于:顺序表

8.3 选择排序

算法思想:每一趟在后面的n - i + 1个待排序列元素中选取关键字最小的元素, 作为有序子序列的第i个元素

①简单选择排序

算法思想:

假设排序表为L1...n,第i趟排序从Li...n中选出关键字最小的元素,并与L(i)交换。

​ 每一趟可以确定一个元素的最终位置

​ 经过n- 1趟,整个序列有序

cpp 复制代码
void SelectSort(int A[], int n){
	for(int i = 0; i < n; i ++){
		int min = i;
		for(int j = i + 1; j < n; j ++){
			if(A[j] < A[min] ) min = j;
		}
		if(min != i) swap(A[i],A[min]);
	}
}
性能:

复杂度:

时间复杂度:O(n^2)

空间复杂度:O(1)

稳定性:不稳定

适用于:链式存储和线性存储的顺序表

②堆排序***

2.1大跟堆建立

算法思想:根大于左右子树,建立的过程让小元素下坠

cpp 复制代码
//建立大根堆
void BuildMaxHeap(int A[], int len){
	for(int i = len/2; i > 0; i--){
		HeadAdjust(A, i , len);
	}
}

//将以k为根的子树调整为大根堆
void HeadAdjust(int A[], int k, int len){
	A[0] = A[k];
	for(int i = 2*k; i <= len; i *= 2){
		if(i < len && A[i] < A[i + 1]) i ++;
		if(A[0] >= A[i]) break;
		else{
			A[k] = A[i];
			k = i;
		}
	}
	A[k] = A[0];
}

例如,将53作为根,调整大根堆。

A0 = Ak = 53

i = 2, Ai < Ai + 1, i =3

53 < 87, Ak = 87, k = 3

一次for循环调整完

第二次for循环

k = 3, i = 6, 65 < 78, i = 7

A0 = 53 < 78, Ak = 78, k = 7

下一次循环,i不满足<len,退出for循环,然后把A0 = 53放入k的位置。

2.2堆排序

算法思想:每一趟将堆顶元素加入有序子序列(与堆的最后一个元素互换),再将待排序序列再次调整为大根堆

大根堆排序最后得到的是一个递增的序列(每次找出最大的堆顶,但是给他换到最下面去了)

cpp 复制代码
//建立大根堆
void BuildMaxHeap(int A[], int len){
	for(int i = len/2; i > 0; i--){
		HeadAdjust(A, i , len);
	}
}

//将以k为根的子树调整为大根堆
void HeadAdjust(int A[], int k, int len){
	A[0] = A[k];
	for(int i = 2*k; i <= len; i *= 2){
		if(i < len && A[i] < A[i + 1]) i ++;
		if(A[0] >= A[i]) break;
		else{
			A[k] = A[i];
			k = i;
		}
	}
	A[k] = A[0];
}

//堆排序的完整逻辑
void HeapSort(int A[], int len){
	BuildMaxHeap(A, len){
		for(int i = len; i > 1; i --){
			swap(A[i], A[1]);
			HeadAdjust(A, 1, i - 1);
		}
	}
}
性能:

时间复杂度:建堆O(n) + 排序O(n*logn) = O(n * logn)

空间复杂度:O(1)

稳定性:不稳定

适用于:线性存储线性表

易考点

(1)下坠一次,若有两个孩子,需要对比两次关键字; 若有一个孩子,需要对比一次

(2)插入:插入元素放到表尾,根据大/小根堆的要求不断上升,直至无法上升 O(log n)

(3)删除:删除元素的位置用表尾元素,然后根据大/小根堆的要求不断下降 O(log n)

(4)大根堆得到的是递增序列; 小根堆得到的是递减序列

8.4 归并排序

核心操作,把数组中的两个有序的组序列合并成一个

cpp 复制代码
int *B = (int *)malloc(n*sizeof(int))//B辅助数组

void Merge(int A[], int low, intmid, int high){
	int i, j k;
	for(k = low; k < high; k++) B[k] = A[k]; //复制A数组中的元素到B
	for(i = low, j = mid + 1, k = i; i<=mid && j <= high; k++){
		if(B[i] <= B[j]) A[k] = B[i++];
		else A[k] = B[j++]
	}
	while(i <= mid) A[k++] = B[i++];//剩余的元素
	while(j <= high) A[k++] = B[j++];

}

voif MergerSort(int A[],int low,int high){
	if(low < high){
		int mid = (high + low)/2;
		MergerSort(A, low, mid - 1);
		MergerSort(A, mid + 1, high);
	}
}
性能

复杂度:时间复杂度:O(n*logn); 空间复杂度:O(n)

稳定性:稳定

适用于:顺序表和链表

8.5 基数排序

不依赖元素之间的比较,而基于关键字各位的大小进行排序

最低位优先法(LSD):按关键字权值递增依次进行排序

关键字有d个分量,各位取值0~r-1

分配O(n) + 收集O®

性能:

时间复杂度O(d(n+r))

空间复杂度O®

稳定的

适用于顺序表和链表

*8.6计数排序

不依赖于比较的算法,对每个待排元素x, C数组先统计各个元素出现的次数, 再遍历一次使得Ci中存储的是 <= i的元素

然后就可以定位元素的位置。

元素取值[0, k)

c 复制代码
void CountSort(int A[], int B[], int n, int k){
	int i, C[k];
    for(i = 0; i < n; i ++) C[i] = 0;
    for(i = 0; i < n; i ++) C[A[i]] ++;
    for(i = 1; i < n; i ++) C[i] += C[i - 1];
    
    for(i = n - 1; i >= 0; i --){//从后往前,确保稳定
        C[A[i]] --;				//本身--
        B[C[A[i]] = A[i];		//C[A[i]]代表次序,A[i]元素本身
    }
}
性能

复杂度:

时间复杂度 O(n + k)

空间复杂度 O(n + k)

稳定的

适用于顺序表

对比

相关推荐
richdata1 小时前
需求预测终极指南:零售商品预测方法、算法与AI实践
人工智能·算法·零售
隔窗听雨眠2 小时前
C语言函数递归从入门到精通(下):性能优化与工程实践
c语言·算法·性能优化
退休倒计时2 小时前
【每日一题】LeetCode 146. LRU 缓存 TypeScript
算法·leetcode·缓存·typescript
珊瑚里的鱼2 小时前
【递归】汉诺塔
算法·深度优先
MrZhao4002 小时前
一个最小 Agent 是怎么跑起来的:Agent Loop 与工具使用全链路
算法
Keven_112 小时前
算法札记:二分
算法·二分
TCW11213 小时前
AI底层系列:用C++实现线性代数的公式推导与算法设计-6.线性方程组的解集
c++·人工智能·算法
luoyayun3613 小时前
从零实现 EBU R128 LUFS 响度分析:K-weighting 滤波、双门限算法
算法·lufs响度分析
小糯米6013 小时前
JS 数组
数据结构·算法·排序算法