第八章 排序

2023-09-20 17:40:05
一、插入排序
  1. 不带哨兵
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;
		}
	}
}
  1. 带哨兵
void InsertSort(int A[], int n){
	int i, j;
	for (i=2; i<=n; i++){
		if (A[i]<A[i-1]){
			A[0] = A[i];
			for (j=i-1; A[0]<A[j]; --j){ // 不用每次循环都判断j>=0
				A[j+1] = A[j];
			}
			A[j+1] = A[0];
		}
	}
}

请添加图片描述

  1. 折半插入排序
    void InsertSort(ing A[], int n){
    	int i, j, low, high, mid;
    	for (i=2; i<=n; i++){
    		A[0] = A[i];
    		low = 1;
    		high = i-1;
    		while (low<=high){
    			mid = (high+low)/2;
    			if (A[mid]>A[0])
    				high = mid - 1;
    			else low = mid + 1;
    		}
    		for (j=i-1; j>=high+1; --j){
    			A[j+1] = A[j];
    		}
    		A[high+1] = A[0];
    	}
    }
    
二、 希尔排序

思想:先追求表中元素部分有序,再逐渐逼近全局有序
这里的A[0]只是暂存单元,不是哨兵(循环语句中会判断若j<=0就到了插入位置)

void ShellSort(int A[], int n){
	int d, i, j;
	for (d=n/2; d>=1; d=d/2)
		for (i=d+1; i<=n; ++i)
			if (A[i]<A[i-d]{
				A[0] = A[i];
				for (j=i-d; j>0 && A[0]<A[j]; i-=d)
					A[j+d] = A[j]; // 同组的记录后移,找寻当前元素的插入位置
				A[j+d] = A[0];
			}
}
三、冒泡排序

每一趟都能使最小的元素被放在最终位置(也可以改动以下代码使每一趟把最大的元素被放在最终位置)

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

void BubbleSort(int A[], int n){
	for (int i=0; i<n-1; i++){
		bool flag = false;
		for (int j=n-1; j>i; j--)
			if (A[j-1]>A[j]){
				swap(A[j-1], A[j]);
				flag = true;
			}
		if (flag==false)
			return;
	}
}
四、快速排序
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;
}
五、简单选择排序(属于选择排序)

每一趟在待排序元素中选取关键字最小的元素加入有序子序列(即有序子序列放在整个序列的开头,每一趟找到的最小元素放在有序子序列的末尾)

void SelectSort(int A[], int n){
	for (int i=0; i<n-1; i++){
		int min = i;
		for (int j=i+1; i<n; j++){
			if (A[j]<A[min])
				min = j;
		}
		if (min!=i)
			swap(A[i], A[min]);
	}
}

void swap(int &a, int &b){
	int temp = a;
	a = b;
	b = temp;
}		
六、堆排序(属于选择排序)

先建堆,再排序。建堆时让编号 ≤ n 2 \le\frac{n}{2} 2n的所有节点依次下坠(自底向上调整各分支节点),下坠规则就是小元素与关键字更大的孩子交换位置。排序时让堆顶元素加入有序子序列(大根堆排序时是让堆顶元素与堆底元素交换),堆底元素换到堆顶后,进行下坠调整,恢复大根堆的特性。将排序过程重复n-1趟

  1. 大根堆
    • 建立大根堆(A[0]初始值为空)
      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];  // A[0]的作用为暂存子树根节点(即要下坠的那个小元素)
      	for (int i=2*k; i<=len; i*2){  // 让i先指向当前节点左孩子
      		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];
      }
      

      这里有个常考考点:若一个节点有两个孩子,则其“下坠”一层,需要对比关键字两次(孩子对比选最大,该节点再与大孩子对比);若下方只有一个孩子,则“下坠”一层只需对比关键字1次

    • 基于建立的大根堆,进行堆排序:每一趟将堆顶元素加入有序子序列(即有序子序列放在整个序列的结尾,每一趟找到的最大元素放在有序子序列的开头),并将待排序元素再次调整为大根堆(又进行一轮小元素不断下坠)
      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);
      	}
      }
      
七、归并排序

归并即把两个或多个已经有序的序列合并成一个

int *B = (int *)malloc(n*sizeof(int)); // 辅助数组B

void Merge(int A[], int low, int mid, 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++];
}

void MergeSort(int A[], int low, int high){
	if (low<high){
		int mid = (low+high)/2;
		MergeSort(A, low, mid);  // 对左半部分归并排序
		MergeSort(A, mid+1, high);  // 对右半部分归并排序
		Merge(A, low, mid, high);  // 归并
	}
}
八、基数排序

基数排序不是基于比较的排序算法
只考手动模拟,几乎不考代码

  1. 基数排序擅长解决的问题
    • 数据元素的关键字可以方便地拆分为d组,且d较小
    • 每组关键字的取值范围不大,即r较小
    • 数据元素个数n较大
  2. 基数排序通常基于链式存储实现:
    • 定义包含r个队列元素的数组,每个队列保存两个指针(*front和*rear),每个队列中的元素包含数据和指向下一个元素的指针
      typedef struct LinkNode{
      	ElemType data;
      	struct LinkNode *next;
      } LinkNode, *LinkList;
      
      typedef struct{
      	LinkNode *front, *rear;
      } LinkQueue;
      

请添加图片描述

九、外部排序
  1. 败者树:
    • 解决的问题:使用多路平衡归并可减少归并趟数,但用老土方法从k个归并段选出一个最小/最大元素需要对比关键字k-1次,构造败者树可以使关键字对比次数减少到 ⌈ l o g 2 k ⌉ \lceil log_2k \rceil log2k
    • 败者树可视为一棵完全二叉树(多了一个头头)。k个叶节点分别对应k个归并段中当前参加比较的元素,非叶子节点用来记忆左右子树中的“失败者”,而让胜者往上继续进行比较,一直到根节点
  2. 置换-选择排序:

算法效率

算法空间复杂度时间复杂度算法稳定性适用性
插入排序 O ( 1 ) O(1) O(1)最好 O ( n ) O(n) O(n);最坏 O ( n 2 ) O(n^2) O(n2);平均 O ( n 2 ) O(n^2) O(n2) (主要来自对比关键字、移动元素,若有n个元素,需要n-1趟处理)稳定
折半插入排序 O ( 1 ) O(1) O(1)最好 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n);最坏 O ( n 2 ) O(n^2) O(n2);平均 O ( n 2 ) O(n^2) O(n2)稳定
希尔排序 O ( 1 ) O(1) O(1)最坏 O ( n 2 ) O(n^2) O(n2)不稳定仅适用于顺序表,不适用于链表
冒泡排序 O ( 1 ) O(1) O(1)最好 O ( n ) O(n) O(n)(有序);最坏 O ( n 2 ) O(n^2) O(n2)(逆序);平均 O ( n 2 ) O(n^2) O(n2)稳定适用于顺序表和链表
快速排序最好 O ( l o g 2 n ) O(log_2n) O(log2n);最坏 O ( n ) O(n) O(n)最好 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)(每次选的pivot都能将序列分成均匀的两部分);最坏 O ( n 2 ) O(n^2) O(n2)(初始有序或逆序);平均 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)不稳定适用于顺序表,不适用于链表
简单选择排序 O ( 1 ) O(1) O(1) O ( n 2 ) O(n^2) O(n2)不稳定顺序表、链表都可以
堆排序 O ( 1 ) O(1) O(1)建堆 O ( n ) O(n) O(n)、排序 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),总时间复杂度 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)不稳定
归并排序 O ( 1 ) O(1) O(1)每一趟的对比次数 O ( n ) O(n) O(n)、趟数 O ( l o g 2 n ) O(log_2n) O(log2n),总时间复杂度 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)稳定
基数排序 O ( r ) O(r) O(r) O ( d ( n + r ) ) O(d(n+r)) O(d(n+r)),其中数据元素的关键字拆分为d组,每组关键字的取值范围为r,数据元素个数为n稳定
更多推荐

第二证券:什么是a股b股?

在我国的股市中,我们经常会听到“A股”和“B股”这两个名词。那么,终究什么是A股和B股呢?首先,A股全称为“A股票”,是指在我国境内上市的以人民币计价的股票。A股首要面向国内出资者,只要具有必定条件的内地居民和安排出资者才可出资A股。此外,A股还分为两种:人民币A股和港币A股。前者是在我国境内发行的A股,后者则是在香港

Redis 面试题——缓存穿透、缓存击穿和缓存雪崩

目录1.缓存穿透2.缓存击穿3.缓存雪崩4.总结参考文章:缓存实战(1)缓存雪崩、缓存击穿和缓存穿透入门简介及解决方案1.缓存穿透(1)问题描述:缓存穿透是指在高并发场景下,大量的请求访问一个不存在于缓存中也不存在于数据库中的数据,导致每次请求都要查询数据库,增加了数据库的负载。通常发生在恶意攻击、频繁访问不存在的数据

OSCP系列靶场-Esay-Moneybox保姆级

OSCP系列靶场-Esay-Moneybox目录OSCP系列靶场-Esay-Moneybox总结准备工作信息收集-端口扫描目标开放端口收集目标端口对应服务探测信息收集-端口测试21-FTP端口的信息收集21-FTP版本版本信息21-FTP端口匿名登录测试(存在)21-FTP端口-文件GET收集21-FTP端口-PUT上

微服务 第一章 Java线程池技术应用

系列文章目录第一章Java线程池技术应用文章目录系列文章目录@[TOC](文章目录)前言1、Java创建线程方式回顾1.1、继承Thread类(只运行一次)1.1.1、改造成主线程常驻,每秒开启新线程运行1.1.2、匿名内部类1.1.3、缺点1.1.4、扩展知识:Java内部类1.1.4.1、静态内部类1.1.4.2、

机器学习实战:Python基于Ridge岭回归进行正则化(十三)

文章目录1.前言1.1岭回归的介绍1.2岭回归的应用2.自定义数据集实战演示2.1导入函数2.2创建数据集2.3alpha=0、1、10、100的分别情况3.Dushanbe_house数据集实战演示3.1导入函数和数据3.2剔除空值及可视化3.3整理数据3.4训练和测试数据集3.5评估数据集4.讨论1.前言1.1岭回

Python 04 之变量【列表,元组,集合,字典,字符串】

😀前言在Python编程语言中,我们经常会遇到各种数据类型和相应的操作方法。理解和掌握这些基本构造是进行有效编程的前提。在本文中,我们将介绍两种非常重要的数据结构-集合和字典,然后我们将深入探讨字符串及其相关的操作和处理方法,包括格式化和切片。我们还将通过示例来详细解释如何使用这些结构和方法,以便你可以在实际编程中轻

yolov5的使用

目录本地的标注数据集工具推荐如下:在线标注工具COCO训练模型用PyTorch训练一个简单的多层感知器(MLP)进行手写数字识别(MNIST数据集)。YOLOv5是一种流行的目标检测算法,可以用于识别和定位图像中的对象。要使用YOLOv5识别老鼠,您需要执行以下步骤:YOLOv5是一种流行的深度学习算法,用于目标检测和

金融业需要的大模型,是一个系统化工程

今年年初,在AIGC刚刚开始爆火的时候,我们曾经采访过一位AI领域的专家。当我们提问哪个行业将率先落地大模型时,他毫不犹豫地说道:“金融。”金融行业场景多、数据多、知识多,这样的“三多”特点让其成为AI大模型发挥价值的天选。与此同时,金融场景专业度高,业务复杂,在风控、安全、效率等方面有着严格的要求,初出茅庐的大模型,

GaussDB数据库SQL系列-数据去重

目录一、前言二、数据去重应用场景三、数据去重案例(GaussDB)1、示例场景描述2、定义重复数据3、制定去重规则4、创建测试数据(GaussDB)5、编写去重方法(GaussDB)6、附:全字段去重四、数据去重效率提升建议五、总结一、前言数据去重在数据库中是比较常见的操作。复杂的业务场景、多业务线的数据来源等等,都会

【深度学习】Pytorch 系列教程(十):PyTorch数据结构:2、张量操作(Tensor Operations):(4)索引和切片详解

目录一、前言二、实验环境三、PyTorch数据结构0、分类1、张量(Tensor)2、张量操作(TensorOperations)1.数学运算2.统计计算3.张量变形4.索引和切片使用索引访问单个元素使用切片访问子集使用索引和切片进行修改布尔索引(Booleanindexing)高级切片(Advancedslicing

以AI对抗AI,大模型安全的“进化论”

点击关注文丨刘雨琦,编|王一粟“互联网时代,我们是更危险,还是更安全?”2016年,互联网正值高速发展之际,电梯广告经常出现这几个大字,两行标语,从病毒木马到网络诈骗,对于安全的思考、安全防范技术的建立一直在与科技发展赛跑。同样,大模型时代发展的早期,也引发了许多安全考量。英特网被发明的十年后,互联网防护技术和产业链才

热文推荐