[数据结构]排序

本篇主要是对常见排序的分类和一些排序的详解

一、插入排序

1.直接插入排序

// 直接插入排序函数
void insertionSort(int arr[], int n) {int i, key, j;for (i = 1; i < n; i++) {key = arr[i]; // 取出待排序的元素j = i - 1;// 将大于key的元素向后移动一位while (j >= 0 && arr[j] > key) {arr[j + 1] = arr[j];j = j - 1;}arr[j + 1] = key; // 将key插入到正确的位置}
}

这个排序的时间复杂度为O(n^2)空间复杂度为O(1)

2.希尔排序

#include <stdio.h>
// 希尔排序函数,接受一个整数数组和数组长度作为参数
void shell_sort(int arr[], int n) 
{// 外层循环控制不同的间隔值gapfor (int gap = n / 3 + 1; gap > 0; gap /= 3) {// 内层循环遍历数组中的元素,从索引gap开始到数组末尾for (int i = gap; i < n; i++) {int temp = arr[i]; // 保存当前元素的值int j;// 比较当前元素与间隔为gap的前一个元素的大小,如果前一个元素较大,则交换它们的位置for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {arr[j] = arr[j - gap];}arr[j] = temp; // 将保存的当前元素值赋给正确的位置}}
}

这个排序上回我们讲过了,空间复杂度为O(1),时间复杂度为O(n^1.3)。

二、选择排序

1.选择排序

// 选择排序函数
void selectionSort(int arr[], int n) {int i, j, minIndex, temp;for (i = 0; i < n - 1; i++) {minIndex = i; // 记录当前最小值的索引for (j = i + 1; j < n; j++) {if (arr[j] < arr[minIndex]) {minIndex = j; // 更新最小值索引}}// 交换当前元素与最小值元素temp = arr[i];arr[i] = arr[minIndex];arr[minIndex] = temp;}
}

这个排序的思路也很简单,时间复杂度为O(N^2),空间复杂度是O(1)

2.堆排序

// 调整堆
void adjustHeap(int arr[], int i, int length) {int temp = arr[i]; // 取出当前元素for (int k = 2 * i + 1; k < length; k = 2 * k + 1) {if (k + 1 < length && arr[k] < arr[k + 1]) { // 如果右子节点大于左子节点,k指向右子节点k++;}if (arr[k] > temp) { // 如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)arr[i] = arr[k];i = k;} else {break;}}arr[i] = temp; // 将temp值放到最终的位置
}// 堆排序
void heapSort(int arr[], int length) {// 1.构建大顶堆for (int i = length / 2 - 1; i >= 0; i--) {adjustHeap(arr, i, length);}// 2.调整堆结构+交换堆顶元素与末尾元素for (int j = length - 1; j > 0; j--) {// 交换堆顶元素和末尾元素int temp = arr[j];arr[j] = arr[0];arr[0] = temp;// 重新对堆进行调整adjustHeap(arr, 0, j);}
}

堆排序的时间复杂度O(N*logN),空间复杂度O(1)

三、交换排序

1,冒泡排序

// 冒泡排序函数
void bubble_sort(int arr[], int n) {for (int i = 0; i < n - 1; i++) { // 外层循环控制排序趟数for (int j = 0; j < n - 1 - i; j++) { // 内层循环控制每趟比较的次数if (arr[j] > arr[j + 1]) { // 如果前一个元素大于后一个元素,交换它们的位置int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}
}

这个排序大家肯定都非常熟悉了,时间复杂度是O(n^2),空间复杂度是O(1)

2.快速排序

这个排序我们没讲过这里我们来讲一讲。

快速排序的整体思路就是保证基准值的左边的数小于基准值,右边大于基准值。

首先我们拥有如下数组

我们先选定一个基准值,(这里我们讲最基础的)我们选择第一个数作为基准值。

然后我们先从右往左走找小于基准值的值

找到后我们从左往右找大于基准值的

然后交换两个值

接着我们再重复之前的操作,直到相遇

然后将相遇点和基准值交换

然后我们可以得到基准值的下标,然后最左端不变,以基准值的下标-1为最右端,以基准值的下标为最左端,最右端不变把原来的数据一分为二:

然后再分别进行快速排序,以此类推,直到所有递归都返回.

代码示例:

void QuickSort(int* a, int left, int right)
{// 区间只有一个值或者不存在就是最小子问题if (left >= right)return;int begin = left, end = right;int keyi = left;while (left < right){// right先走,找小while (left < right && a[right] >= a[keyi]){--right;}// left再走,找大while (left < right && a[left] <= a[keyi]){++left;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);keyi = left;// [begin, keyi-1]keyi[keyi+1, end]QuickSort(a, begin, keyi - 1);QuickSort(a, keyi+1, end);
}

快速排序的空间复杂度O(logN)时间复杂度是O(N*logN)。有意思的是其实快速排序的时间复杂度不是最坏情况,最坏情况应该是O(N^2),但是出现的概率很小。其次我们可以通过各种方法来改进

举几个例子,就不展开讲了:

//三数取中
int GetMidi(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}else if (a[left] > a[right]){return left;}else{return right;}}else{if (a[mid] > a[right]){return mid;}else if (a[left] < a[right]){return left;}else{return right;}}
}

我们通过三数取中得到key

在快排开头加上这个,就可以很好的减少我们出现最坏情况

int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);

如果觉得三数取中比较复杂,我们可以去随机数作为key,虽然效率不高,但是写起来方便,也能减少出现最坏情况

int randi = rand() % (right - left + 1);
randi += left;
Swap(&a[left], &a[randi]);

快速排序其实分好几种,这里我再展示一种前后指针法,个人感觉写起来来简单一点

void QuickSort2(int* a, int left, int right)
{if (left >= right)return;int keyi = left;int prev = left;int cur = left+1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]);++cur;}Swap(&a[keyi], &a[prev]);keyi = prev;QuickSort2(a, left, keyi - 1);QuickSort2(a, keyi + 1, right);
}

这里再展示一下非递归的快速排序,感兴趣的可以自己看下(注意要结合栈)

void QuickSortNonR(int* a, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st, left);
StackPush(&st, right);
while (StackEmpty(&st) != 0)
{right = StackTop(&st);StackPop(&st);left = StackTop(&st);StackPop(&st);if(right - left <= 1)continue;int div = PartSort1(a, left, right);// 以基准值为分割点,形成左右两部分:[left, div) 和 [div+1, right)StackPush(&st, div+1);StackPush(&st, right);StackPush(&st, left);StackPush(&st, div);
}StackDestroy(&s);
}

四、归并排序

基本思想:
归并排序( MERGE-SORT )是建立在归并操作上的一种有效的排序算法 ,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
我将会把递归和非递归的写法展示给大家
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,end1 = mid;int begin2 = mid + 1,end2 = end;int i = begin;// 依次比较,取小的尾插tmp数组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));
}

这部分代码实现了归并排序的递归版本。函数_MergeSort接受一个整数数组a、起始索引begin、结束索引end和一个临时数组tmp作为参数。首先判断是否只有一个元素,如果是则直接返回。然后计算中间索引mid,将数组分为两部分进行递归排序。接下来进行归并操作,将两个有序的子数组合并成一个有序的数组,并将结果存储在tmp数组中。最后将tmp数组的内容复制回原数组a

void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}_MergeSort(a, 0, n-1, tmp);free(tmp);tmp = NULL;
}

这部分代码是归并排序的入口函数,调用了递归版本的_MergeSort函数。首先分配一个临时数组tmp,如果分配失败则输出错误信息并返回。然后调用_MergeSort函数进行排序,最后释放临时数组的内存。

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

这部分代码实现了归并排序的迭代版本。函数MergeSortNonR接受一个整数数组a和数组长度n作为参数。首先分配一个临时数组tmp,如果分配失败则输出错误信息并返回。然后使用迭代的方式逐步增大间隔gap,对数组进行分组归并排序。最后释放临时数组的内存


本篇不出意料,是初阶数据结构的最后一篇,下一篇我们要开始C++了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/293930.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows Server 2022 使用ApacheDS用户远程桌面登录服务器

Windows Server 2022 使用ApacheDS用户远程桌面登录服务器 1、接上篇 Windows Server 2022 使用ApacheDS用户认证 使用Administrator用户远程登录192.168.1.100windows server&#xff0c;打开pGina软件 2、输入刚刚在ApacheDS中的新添加的用户测试一下&#xff0c;会自动添加…

设计模式之抽象工厂模式精讲

概念&#xff1a;为创建一组相关或相互依赖的对象提供一个接口&#xff0c;而且无须指定他们的具体类。 抽象工厂模式是工厂方法模式的升级版本。在存在多个业务品种或分类时&#xff0c;抽象工厂模式是一种更好的解决方式。 抽象工厂模式的UML类图如下&#xff1a; 可以看…

C++教学——从入门到精通 4.setw()语句

这次玩点新鲜的------setw() 这家虎是啥呢&#xff1f; 我们编程输出的时候总是要输出空格&#xff0c;但有些时候又点的手都麻了 这时setw语句就派上用场了 具体怎么用呢&#xff1f; 如下图 #include"iostream"// #include"iomanip"// bits/stdc…

OSCP靶场--RubyDome

OSCP靶场–RubyDome 考点(CVE-2022-25765 suid ruby提权) 1.nmap扫描 ┌──(root㉿kali)-[~/Desktop] └─# nmap -Pn -sC -sV 192.168.249.22 --min-rate 2500 Starting Nmap 7.92 ( https://nmap.org ) at 2024-03-29 00:28 EDT Nmap scan report for 192.168.249.22 Hos…

配置文件乱码

1、改UTF-8 &#xff08;1&#xff09;已经创建的项目 (2)新项目也改一下

YOLOv9改进策略 :主干优化 | 极简的神经网络VanillaBlock 实现涨点 |华为诺亚 VanillaNet

💡💡💡本文改进内容: VanillaNet,是一种设计优雅的神经网络架构, 通过避免高深度、shortcuts和自注意力等复杂操作,VanillaNet 简洁明了但功能强大。 💡💡💡引入VanillaBlock GFLOPs从原始的238.9降低至 165.0 ,保持轻量级的同时在多个数据集验证能够高效涨点…

北京WordPress建站公司

北京wordpress建站&#xff0c;就找北京wordpress建站公司 http://wordpress.zhanyes.com/beijing

java--this关键字

this代表当前对象&#xff0c;this后面可以加&#xff1a; 1、属性--> this.属性&#xff1a; 当方法中的局部变量与成员变量名称相同时&#xff0c;成员变量必须用this&#xff0c;其它情况的this可以省略 2、方法--this.方法&#xff1a;静态方法中不能使用this关键字&…

非关系型数据库之Redis配置与优化

一、关系数据库与非关系型数据库 1.1关系型数据库 关系型数据库是一个结构化的数据库&#xff0c;创建在关系模型&#xff08;二维表格模型&#xff09;基础上一般面向于记录。SQL语句&#xff08;标准数据查询语言&#xff09;就是一种基于关系型数据库的语言&#xff0c;用…

求组合数I(acwing)

题目描述&#xff1a; 给定n组询问&#xff0c;每组询问给定两个整数a&#xff0c;b&#xff0c;请你输出Ca^b mod(1e97)的值。 输入格式: 第一行包含整数n。 接下来n行&#xff0c;每行包含一组a和b。 输出格式: 共n行&#xff0c;每行输出一个询问的解。 …

什么是ISP住宅IP?相比于普通IP它的优势是什么?

什么是ISP住宅IP&#xff1f; ISP住宅IP是指由互联网服务提供商&#xff08;ISP&#xff09;分配给住宅用户的IP地址。它是用户在家庭网络环境中连接互联网的标识符&#xff0c;通常用于上网浏览、数据传输等活动。ISP住宅IP可以是动态分配的&#xff0c;即每次连接时都可能会…

java基础动态代理和反射(一)-- 动态代理,反射,动态语言,静态语言

动态代理 代理&#xff1a;本来应该自己做的事情&#xff0c;却请来了别人来做&#xff0c;被请的人就是代理对象。动态代理&#xff1a;在程序运行过程中产生的这个对象。动态代理其实就是通过反射来生成一个代理。 import java.lang.reflect.InvocationHandler; import jav…

苹果推出Swift开发教程 无需编码知识小白也能学

简介 苹果推出Swift开发教程&#xff0c;教授开发者如何使用 Swift、SwiftUI 和 Xcode 开发 iOS 应用。从基本的界面设计到复杂的数据建模和空间计算。据苹果公司称&#xff0c;网站上提供的教程 "适合所有人"&#xff0c;即使是那些没有任何编码经验的人。教程提供…

让工作自动化起来!无所不能的Python

让工作自动化起来&#xff01;无所不能的Python 让工作自动化起来&#xff01;无所不能的Python编辑推荐内容简介作者简介前言为什么要写这本书读者对象如何阅读本书 博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客&#x1f466;&#x1f3fb; 《java 面试题大全…

Java中常见的锁策略

目录 乐观锁 vs 悲观锁 悲观锁: 乐观锁&#xff1a; 重量级锁 vs 轻量级锁 ⾃旋锁&#xff08;Spin Lock&#xff09; 公平锁 vs 非公平锁 可重⼊锁 vs 不可重入锁 读写锁 乐观锁 vs 悲观锁 悲观锁: 总是假设最坏的情况&#xff0c;每次去拿数据的时候都认为别…

【DETR系列目标检测算法代码精讲】01 DETR算法03 Dataloader代码精讲

与一般的Dataloader的区别在于我们对图像进行了随机裁剪&#xff0c;需要进行额外的操作才能将其打包到dataloader里面 这一段的代码如下&#xff1a; if args.distributed:sampler_train DistributedSampler(dataset_train)sampler_val DistributedSampler(dataset_val, shu…

C语言动态内存讲解+通讯录2.0

文章目录 前文malloc和freecallocrealloc枚举常量的简单说明及使用 通讯录2.0动态开辟通讯录,满了就扩容保存数据和载入数据 通讯录2.0演示推荐好用的软件 前文 本文主要介绍动态开辟的几个函数,以及改进之前的通讯录。 我们局部变量等是在栈区上开辟空间的,而我们动态开辟的空…

非wpf应用程序项目【类库、用户控件库】中使用HandyControl

文章速览 前言参考文章实现方法1、添加HandyControl包&#xff1b;2、添加资源字典3、修改资源字典内容 坚持记录实属不易&#xff0c;希望友善多金的码友能够随手点一个赞。 共同创建氛围更加良好的开发者社区&#xff01; 谢谢~ 前言 wpf应用程序中&#xff0c;在入口项目中…

Linux 给网卡配置ip

ip addr | grep eth9 ifconfig eth9 10.0.0.2 netmask 255.255.255.0 up

linux安装git

一、下载git 注意&#xff1a;不要下载最新版本的git&#xff0c;否则可能安装会失败&#xff0c;缺失很多依赖文件&#xff0c;解决起来费时费力&#xff0c;还可能不成功 尽量下载前几年的&#xff0c;甚至10年前的都可以 下载地址&#xff1a;https://mirrors.edge.kerne…