数据结构—排序算法交换排序(冒泡快排)

news/2024/10/18 7:49:28/

目录

1.交换排序—冒泡排序

1.1冒泡排序基本思想

1.2冒泡排序的实现

2.交换排序—快速排序

1.1快速排序基本思想

1.2基准值划分—分析

1. hoare版:

2. 挖坑法:

3. 前后指针版本

1.3 hoare快排的具体实现

1.4 挖坑法快排的具体实现

1.5 前后指针版本快排的具体实现

1.6 快速排序的优化

优化1:三数取中(提升效率,防止有序序列栈溢出)

优化2:小区间优化(极大减少递归次数)

1.7 快排非递归

1.8 快速排序的特性总结


1.交换排序—冒泡排序

1.1冒泡排序基本思想

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

1.2冒泡排序的实现

void BubbleSort(int*a, int n)
{int i = 0;for (i = 0; i < n - 1; i++)//控制趟数{int flag = 1;//假设已经有序int j = 0;for (j = 0; j < n - 1 - i; j++)//控制比较次数{if (a[j] > a[j + 1]){flag = 0;int temp = a[j];a[j] = a[j + 1];a[j + 1] = temp;}}if (1 == flag){break;}}
}

冒泡排序的特性总结:

1. 冒泡排序是一种非常容易理解的排序

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1)

4. 稳定性:稳定

2.交换排序—快速排序

1.1快速排序基本思想

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

// 假设按照升序对array数组中[left, right)区间中的元素进行排序
void QuickSort(int array[], int left, int right)
{if (right - left <= 1)return;// 按照基准值对array数组的 [left, right)区间中的元素进行划分int div = partion(array, left, right);// 划分成功后以div为边界形成了左右两部分 [left, div) 和 [div+1, right)// 递归排[left, div)QuickSort(array, left, div);// 递归排[div+1, right)QuickSort(array, div + 1, right);
}

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

1.2基准值划分—分析

将区间按照基准值划分为左右两半部分的常见方式有:  

1. hoare版:

分析图:

hoare版—快排单趟排序实现:

//hoare
int PartSort1(int* a, int left, int right) {int keyi = left;while (left < right) {while (left < right && a[right] >= a[keyi]) {right--;}while (left < right && a[left] <= a[keyi]) {left++;}Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[left]);// [begin , keyi - 1] keyi [keyi + 1,end]keyi = left;return keyi;
}

2. 挖坑法:

 

分析图:

挖坑法—快排单趟排序实现:

//挖坑法
int PartSort2(int* a, int left, int right) {//坑值int key = a[left];//坑位 - left 坑位是动态变化while (left < right) {while (left < right && a[right] >= key) {right--;}//此时将right,大于key的值,填坑到left坑位a[left] = a[right];while (left < right && a[left] <= key) {left++;}//此时left,小于key的值,填坑到right坑位a[right] = a[left];}a[left] = key;//返回最终填坑位置,再次分为[begin , keyi - 1] keyi [keyi + 1,end]return left;
}

3. 前后指针版本

前后指针版本—快排单趟排序实现:

//前后指针版
int PartSort3(int* a, int left, int right) {int keyi = left;int prev = left, cur = left + 1;while (cur <= right) {if (a[cur] < a[keyi] && ++prev != cur) {Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[keyi], &a[prev]);return prev;
}

1.3 hoare快排的具体实现

当上述hoare第一趟排完,key值便到了其顺序的正确位置,此时分为 [begin , keyi - 1] keyi [keyi + 1,end] ,对 keyi 位置左边再次进行排序,其次在对 keyi 位置右边进行排序,再次分为 [begin , keyi - 1] keyi [keyi + 1,end] ,因此采用分治思想,利用递归,实现快排。

void QuickSort(int* a, int begin, int end) {assert(a);//当区间不存在,或者只有一个时,则不需要再处理if (begin >= end) {return;}int left = begin,right = end;int key = a[left];//坑位 - left 坑位是动态变化while (left < right) {while (left < right && a[right] >= key) {right--;}//此时将right,大于key的值,填坑到left坑位a[left] = a[right];while (left < right && a[left] <= key) {left++;}//此时left,小于key的值,填坑到right坑位a[right] = a[left];}a[left] = key;QuickSort(a, begin, left - 1);QuickSort(a, left + 1, end);
}

1.4 挖坑法快排的具体实现

void QuickSort(int* a, int begin,int end){assert(a);//当区间不存在,或者只有一个时,则不需要再处理if (begin >= end) {return;}int key = a[left];//坑位 - left 坑位是动态变化while (left < right) {while (left < right && a[right] >= key) {right--;}//此时将right,大于key的值,填坑到left坑位a[left] = a[right];while (left < right && a[left] <= key) {left++;}//此时left,小于key的值,填坑到right坑位a[right] = a[left];}a[left] = key;QuickSort(a, begin, key - 1);QuickSort(a, key + 1, end);
}

1.5 前后指针版本快排的具体实现

void QuickSort(int* a, int begin, int end) {assert(a);//当区间不存在,或者只有一个时,则不需要再处理if (begin >= end) {return;}int left = begin, right = end;int keyi = left;int prev = left, 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;QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

1.6 快速排序的优化

优化1:三数取中(提升效率,防止有序序列栈溢出)

分析:

  1. 如果每次所选key值为中位数,此时效率较高,数据会快速趋向于有序。
  2. 如果一组数据,完全有序,但是快排仍需要递归从第一个key值进行检测,检测完在检测剩下 N-1 个数据,在检测剩下 N-2 个数据 ...最终检测剩下一个数据时,递归层层返回。效率极低,时间复杂度为O(N^2)。
  3. 如果数据量过大,层层递归检测,有可能会出现栈溢出情景。

解决方法:

  1. 随机选key,但是选中小概率仍为不确定
  2. 三数取中,第一个,中间,后一个,选不是最大的,也不是最小的
int GetMidIndex(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 right;}else {return left;}}else {//a[left] > a[mid]  a[mid] < a[right]if (a[mid] > a[right]) {return mid;}else if (a[left] < a[right]) {return left;}else {return right;}}
}//前后指针版
int PartSort3(int* a, int left, int right) {int mid = GetMidIndex(a, left, right);Swap(&a[left], &a[mid]);int keyi = left;int prev = left, cur = left + 1;while (cur <= right) {if (a[cur] < a[keyi] && ++prev != cur) {Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[keyi], &a[prev]);return prev;
}void QuickSort(int* a, int begin,int end){assert(a);//当区间不存在,或者只有一个时,则不需要再处理if (begin >= end) {return;}int keyi = PartSort3(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

优化2:小区间优化(极大减少递归次数)

分析:

当一组数据,不断找key值,划分左右区间,假设当区间划分数据只有10个数时,递归如下图所示

 仍然需要递归10多次,此时便可对其进行优化。

结论:

当递归划分小区间,区间比较小的时候,就不再递归划分去排序这个区间。可以考虑直接用其他排序对小区间进行处理。

解决方案:

仍采用三数取中优化,同时递归分区为小区间采用插入排序

void InsertSort(int* a, int n) {for (int i = 0; i < n - 1; i++) {int end = i;int temp = a[end + 1];while (end >= 0) {if (temp < a[end]) {a[end + 1] = a[end];end--;}else {break;}}a[end + 1] = temp;}
}int GetMidIndex(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 right;}else {return left;}}else {//a[left] > a[mid]  a[mid] < a[right]if (a[mid] > a[right]) {return mid;}else if (a[left] < a[right]) {return left;}else {return right;}}
}//前后指针版
int PartSort3(int* a, int left, int right) {int mid = GetMidIndex(a, left, right);Swap(&a[left], &a[mid]);int keyi = left;int prev = left, cur = left + 1;while (cur <= right) {if (a[cur] < a[keyi] && ++prev != cur) {Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[keyi], &a[prev]);return prev;
}void QuickSort(int* a, int begin, int end) {assert(a);//当区间不存在,或者只有一个时,则不需要再处理if (begin >= end) {return;}if (end - begin > 10) {int keyi = PartSort3(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);}else {InsertSort(a + begin, end - begin + 1);//对小区间前 n 个数进行排序}
}

1.7 快排非递归

需要掌握递归改非递归,原因如下:

递归大问题,极端场景下,如果递归深度太深,就会出现栈溢出

改动方法:

1、直接改为循环——如斐波那契数列、归并排序

2、用数据结构栈模拟递归过程

3、用数据结构队列模拟递归过程

3、也可以采用其他数据结构模拟递归过程,实现非递归快排

int GetMidIndex(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 right;}else {return left;}}else {//a[left] > a[mid]  a[mid] < a[right]if (a[mid] > a[right]) {return mid;}else if (a[left] < a[right]) {return left;}else {return right;}}
}//前后指针版
int PartSort3(int* a, int left, int right) {int mid = GetMidIndex(a, left, right);Swap(&a[left], &a[mid]);int keyi = left;int prev = left, cur = left + 1;while (cur <= right) {if (a[cur] < a[keyi] && ++prev != cur) {Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[keyi], &a[prev]);return prev;
}
void QuickSortNonR(int* a, int begin, int end) {assert(a);ST st;StackInit(&st);StackPush(&st, end);StackPush(&st, begin);while (!StackEmpty(&st)) {int left = StackPop(&st);int right = StackPop(&st);if (right - left < 10) {InsertSort(a + left, right - left + 1);continue;}int keyi = PartSort3(a, left, right);if (keyi + 1 < right) {StackPush(&st, right);StackPush(&st, keyi + 1);}if (left < keyi - 1) {StackPush(&st, keyi - 1);StackPush(&st, left);}}StackDestory(&st);
}

1.8 快速排序的特性总结

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(logN)

4.  稳定性:不稳定


http://www.ppmy.cn/news/80304.html

相关文章

网安面试题大全(附答案)

本文面试题汇总&#xff1a; 防范常见的 Web 攻击 重要协议分布层 arp协议的工作原理 rip协议是什么&#xff1f;rip的工作原理 什么是RARP&#xff1f;工作原理 OSPF协议&#xff1f;OSPF的工作原理 TCP与UDP区别总结 什么是三次握手四次挥手&#xff1f; tcp为什么要三次握手…

Python数据结构与算法篇(十五)-- 二叉树的遍历:深度优先搜索与广度优先搜索

本篇开始总结二叉树的常用解题技巧&#xff0c;二叉树的顺序遍历和层序遍历刚好对应深度优先搜索和广度优先搜索。 1 顺序遍历 题目列表 144. 前序遍历145. 二叉树的后序遍历 94. 二叉树的中序遍历 144. 二叉树的前序遍历 给你二叉树的根节点 root &#xff0c;返回它…

SpringBoot实现限流注解

SpringBoot实现限流注解 在高并发系统中&#xff0c;保护系统的三种方式分别为&#xff1a;缓存&#xff0c;降级和限流。 限流的目的是通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统&#xff0c;一旦达到限制速率则可以拒绝服务、排队或等待…

基于中文在线文档的Polars工具介绍

Polars学习简介 Polars是一个能够提取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;与加载&#xff08;Load&#xff09;大规模数据集的工具&#xff08;快速多线程、单指令多数据流、延迟/即时执行、查询优化、混合流等&#xff09;。根据官方开发…

网络安全的学习路线是怎么样的?

在众多高大上的学习路线指导中&#xff0c;尝试做一股清流&#xff0c;把要讲清楚的都讲清楚&#xff0c;该学些什么&#xff0c;学到哪个程度进入到下一阶段的学习这些才是最重要的。 在学习之前首先要做好学习的系统规划&#xff1a; 1.目前市场需求主流的岗位里&#xff0…

Python3 命名空间和作用域

在Python中&#xff0c;命名空间&#xff08;Namespace&#xff09;是一个用于存储变量名称和其对应对象的系统。它提供了一种在程序中组织和访问变量的方式&#xff0c;以防止命名冲突并提供代码模块化的能力。 Python中的命名空间可以被视为一个字典&#xff0c;其中变量名称…

Android 系统内的守护进程 - main类服务(3) : installd

声明 只要是操作系统,不用说的就是其中肯定会运行着一些很多守护进程(daemon)来完成很多杂乱的工作。通过系统中的init.rc文件也可以看出来,其中每个service中就包含着系统后台服务进程。而这些服务被分为:core类服务(adbd/servicemanager/healthd/lmkd/logd/vold)和mai…

今日单词|长期主义 (Day 1)

aquifier n.含水层 replenishsupplement vt.补充 oxytocin n.催产素 heyday n.全盛时期 In its heyday, the company ran trains every fifteen minutes. desalination n. desalinate salination salinate salt n. Its too salty. savory. a.令人愉快的、可口的 savor all …