NzN的数据结构--交换排序

2024-04-12 12:04
文章标签 数据结构 排序 交换 nzn

本文主要是介绍NzN的数据结构--交换排序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        篇接上文,今天要学习的就是交换排序,这么励志的日更博主,你怎么能不三连一下呢?

目录

一、基本思想

二、冒泡排序

三、快速排序 

1. hoare版本

2. 挖坑法

3. 前后指针法

4. hoare版本优化

5. 非递归实现快速排序


一、基本思想

        所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。 

二、冒泡排序

 

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
void BubbleSort(int* a, int n)
{for (int j = 0; j < n - 1; j++){for (int i = 1; i < n - j; i++){int flag = 0;if (a[i - 1] > a[i]){Swap(&a[i - 1], &a[i]);flag = 1;//发生交换就把flag改成1}//说明数组本来就有序if (flag == 0)break;}}
}

冒泡排序的特性总结:

  • 容易理解
  • 时间复杂度:
  • 空间复杂度:
  • 稳定性:稳定

三、快速排序 

        快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

        快速排序递归实现的主框架与二叉树前序遍历规则非常像,在写递归框架时可根据二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。将区间按照基准值划分为左右两半部分的常见方式有:

1. hoare版本

//快速排序(hoare版本)
void Quicksort(int* a, int begin, int end)
{if (begin >= end){return;}int left = begin;int right = end;int key = begin;//把begin的位置作为key//小于a[key]  a[key]  大于a[key](升序为例)while (left < right){//循环里依旧需要加上left < right//防止right--和left++导致left < right不成立//当left找到大于key的,且right在小于key的位置,两个交换while (left < right && a[right] >= a[key]){right--;}while (left < right && a[left] <= a[key]){left++;}Swap(&a[right], &a[left]);}Swap(&a[left], &a[key]);key = left;//更新key所在的位置,a[key]此时元素位置固定//以key为分界,左右两边分别再次进行快速排序Quicksort(a, begin, key - 1);Quicksort(a, key + 1, end);
}

Q为什么left和right相遇时所在的位置一定比a[key]小?

A因为right先走,right负责找比a[key]小的位置,left跟right相遇时,right肯定已经停下来了,也就是right停在了比a[key]小的位置。

2. 挖坑法

void QuickSortHole(int* a, int begin, int end) 
{if (begin >= end) {return; //递归结束条件}int key = a[begin]; //选取第一个元素作为keyint left = begin;int right = end;//首元素作为坑位,把首元素取出,下标为0的位置变成坑位//right先走,找到小于key的,就把这个数放到首元素,right所在位置变成坑位//left再走,找到大于key的,放到坑位里,left所在位置再变成坑位//最后也能够达成 小于key  key  大于keywhile (left < right) {while (left < right && a[right] >= key) {right--;}a[left] = a[right]; //将找到的较小元素填到左边的坑里,此时右边形成一个新的坑while (left < right && a[left] <= key) {left++;}a[right] = a[left];}a[left] = key;QuickSortHole(a, begin, left - 1);QuickSortHole(a, left + 1, end);
}

3. 前后指针法

        这种方法是我们最为推荐的!!

void QuickSort_p(int* a, int begin, int end) 
{if (begin >= end) {return;}//首元素作为a[key],a[key]保持不动//prev从首元素出发,cur从首元素的后一个元素开始走//cur≥prev,cur++//cur<prev,prev++,交换cur和prev的值,cur++//cur走到空的时候,比较prev所在位置的值和首元素a[key]//prev<a[key]就交换int prev = begin;int cur = begin + 1;int key = begin;while (cur <= end){if (a[cur] < a[key] && ++prev != cur) {Swap(&a[prev], &a[cur]);}++cur;}Swap(&a[key], &a[prev]);key = prev;QuickSortHole(a, begin, key - 1);QuickSortHole(a, key + 1, end);
}

4. hoare版本优化

        hoare版本在数组有序时为最坏情况,时间复杂度为 。因此可以在数组中随意选一个数作为a[key]。

void Quicksort(int* a, int begin, int end)
{if (begin >= end){return;}//明确left不一定从0开始,end也不一定是数组的最后一个下标int left = begin;int right = end;//选[left,right]区间中的随机数做keyint randi = rand() % (right - left + 1);//这一步得到的randi不一定在区间内randi += left;//能确保randi在区间内Swap(&a[left], &a[randi]);int key = left;//小于a[key]  a[key]  大于a[key](升序为例)while (left < right){//循环里依旧需要加上left < right//防止right--和left++导致left < right不成立//当left找到大于key的,且right在小于key的位置,两个交换while (left < right && a[right] >= a[key]){right--;}while (left < right && a[left] <= a[key]){left++;}Swap(&a[right], &a[left]);}Swap(&a[left], &a[key]);randi = left;//更新key所在的位置,a[key]此时元素位置固定//以key为分界,左右两边分别再次进行快速排序Quicksort(a, begin, randi - 1);Quicksort(a, randi + 1, end);
}

        除此之外,还有一种更为经典的写法—三数取中法。

int Getmidi(int* a, int left, int right)
{//数组有序时,midi就是key,这样就解决了hoare版本的最坏情况int mid = (left + right) / 2;//比较begin、midi和end对应元素的大小,选择位于中间大小的作为a[key]if (a[left] < a[mid]){if (a[mid] < a[right])return mid;else if (a[left] > a[right])return left;elsereturn right;}else //a[left] > a[mid]{if (a[mid] > a[right])return mid;else if (a[right] < a[left])return right;elsereturn left;}
}void Quicksort(int* a, int begin, int end)
{if (begin >= end){return;}int left = begin;int right = end;//小区间走插入排序if (right - left + 1 < 15){InsertSort(a + left, right - left + 1);//a+left指向小区间的起始位置//right-left+1是小区间长度}//三数取中int midi = Getmidi(a, left, right);Swap(&a[left], &a[midi]);int key = left;while (left < right){while (left < right && a[right] >= a[key]){right--;}while (left < right && a[left] <= a[key]){left++;}Swap(&a[right], &a[left]);}Swap(&a[left], &a[key]);key = left;Quicksort(a, begin, key - 1);Quicksort(a, key + 1, end);
}

5. 非递归实现快速排序

        我们前面讲过,当我们需要把递归算法改成非递归算法时,经常会用到栈,这里就需要用到栈的内容。

//非递归实现快速排序
//深度较大时,递归效率很低,可能会出现栈溢出
//递归->非递归有两种方法:变成循环;使用栈
void QuicksortST(int* a, int left, int right)
{//核心:栈存区间,取栈顶区间进行单趟排序//然后右左子区间入栈ST st;STInit(&st);//先入右,再入左,把数组元素的首尾全部入栈STPush(&st, right);STPush(&st, left);while (!STEmpty(&st)){//先压入right,先出的就是leftint begin = STTop(&st);STPop(&st);//再出的才是rightint end = STTop(&st);STPop(&st);//单趟排序int prev = begin;int cur = begin + 1;int key = begin;while (cur <= end){if (a[cur] < a[key] && ++prev != cur){Swap(&a[prev], &a[cur]);}++cur;}Swap(&a[key], &a[prev]);key = prev;//key到达了正确的位置,对两边进行分别排序//对大于a[key]的数据进行排序if (key + 1 < end){STPush(&st, key + 1);STPush(&st, end);}//对小于a[key]的数据进行排序if (key - 1 > begin) {STPush(&st, key - 1);STPush(&st, begin);}}STDestroy(&st);
}

        以上就是交换排序的内容,大家下去重点自主实现快速排序,下一节我们向大家介绍归并排序!记得三连哦!!!

这篇关于NzN的数据结构--交换排序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/897077

相关文章

Python中lambda排序的六种方法

《Python中lambda排序的六种方法》本文主要介绍了Python中使用lambda函数进行排序的六种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们... 目录1.对单个变量进行排序2. 对多个变量进行排序3. 降序排列4. 单独降序1.对单个变量进行排序

关于Java内存访问重排序的研究

《关于Java内存访问重排序的研究》文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则... 目录什么是重排序重排序图解重排序实验as-if-serial语义内存访问重排序与内存可见性内存访问重排序与Java内存模型重排序示意表内存屏障内存屏障示意表Int

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

hdu 1285(拓扑排序)

题意: 给各个队间的胜负关系,让排名次,名词相同按从小到大排。 解析: 拓扑排序是应用于有向无回路图(Direct Acyclic Graph,简称DAG)上的一种排序方式,对一个有向无回路图进行拓扑排序后,所有的顶点形成一个序列,对所有边(u,v),满足u 在v 的前面。该序列说明了顶点表示的事件或状态发生的整体顺序。比较经典的是在工程活动上,某些工程完成后,另一些工程才能继续,此时

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

《数据结构(C语言版)第二版》第八章-排序(8.3-交换排序、8.4-选择排序)

8.3 交换排序 8.3.1 冒泡排序 【算法特点】 (1) 稳定排序。 (2) 可用于链式存储结构。 (3) 移动记录次数较多,算法平均时间性能比直接插入排序差。当初始记录无序,n较大时, 此算法不宜采用。 #include <stdio.h>#include <stdlib.h>#define MAXSIZE 26typedef int KeyType;typedef char In

【软考】希尔排序算法分析

目录 1. c代码2. 运行截图3. 运行解析 1. c代码 #include <stdio.h>#include <stdlib.h> void shellSort(int data[], int n){// 划分的数组,例如8个数则为[4, 2, 1]int *delta;int k;// i控制delta的轮次int i;// 临时变量,换值int temp;in

学习记录:js算法(二十八):删除排序链表中的重复元素、删除排序链表中的重复元素II

文章目录 删除排序链表中的重复元素我的思路解法一:循环解法二:递归 网上思路 删除排序链表中的重复元素 II我的思路网上思路 总结 删除排序链表中的重复元素 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 图一 图二 示例 1:(图一)输入:head = [1,1,2]输出:[1,2]示例 2:(图

【408数据结构】散列 (哈希)知识点集合复习考点题目

苏泽  “弃工从研”的路上很孤独,于是我记下了些许笔记相伴,希望能够帮助到大家    知识点 1. 散列查找 散列查找是一种高效的查找方法,它通过散列函数将关键字映射到数组的一个位置,从而实现快速查找。这种方法的时间复杂度平均为(