網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
前言
本期為大家?guī)?lái)的是常見排序算法中的交換排序,主要有冒泡排序,快速排序,快排分享了三種算法:挖坑法,左右指針?lè)ǎ昂笾羔樂(lè)ǎ约皟煞N優(yōu)化方式:解決快排最壞情況的“三數(shù)取中”,避免遞歸次數(shù)過(guò)多的"小區(qū)間優(yōu)化",
基本思想:所謂交換,就是根據(jù)序列中兩個(gè)記錄鍵值的比較結(jié)果來(lái)對(duì)換這兩個(gè)記錄在序列中的位 置,交換排序的特點(diǎn)是:將鍵值較大的記錄向序列的尾部移動(dòng),鍵值較小的記錄向序列的前部移 動(dòng)。
1.交換排序——冒泡排序
冒泡排序(Bubble?Sort)基本思想:?冒泡排序,類似于水中冒泡,較大的數(shù)沉下去,較小的數(shù)慢慢冒起來(lái),假設(shè)從小到大,即為較大的數(shù)慢慢往后排,較小的數(shù)慢慢往前排。直觀表達(dá),每一趟遍歷,將一個(gè)最大的數(shù)移到序列末尾。也是一種簡(jiǎn)單直觀的排序算法。它重復(fù)地走訪過(guò)要排序的數(shù)列,一次比較兩個(gè)元素,將他們之間小的,或者大的值交換過(guò)來(lái)。遍歷數(shù)列的工作是重復(fù)地進(jìn)行,直到?jīng)]有再需要交換的,也就是說(shuō)該數(shù)列已經(jīng)排序完成。這個(gè)算法的名字由來(lái)是因?yàn)樵叫〉脑貢?huì)經(jīng)由交換慢慢"浮"到數(shù)列的頂端。
1.1 算法思想
比較相鄰的元素,如果前一個(gè)比后一個(gè)大,交換之。
第一趟排序第i個(gè)和第i+1個(gè)比較與交換,隨后第i+1個(gè)和第i+2個(gè)一對(duì)比較交換,這樣直到倒數(shù)第n-1個(gè)和最后n個(gè),將最大的數(shù)移動(dòng)到最后一位。
第二趟將第二大的數(shù)移動(dòng)至倒數(shù)第二位……
1.2 動(dòng)圖演示
?算法實(shí)現(xiàn):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define N 10
Swap(int *p1, int * p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void Print(int *a)
{
for (int i=0;i<N;i++)
{
printf("%d ",a[i]);
}
}
void BubbleSort(int* a, int n)
{
for (int j=0;j<n;++j)
{
int size = 0;
for (int i=1;i<N-j;++i)
{
if (a[i-1]>a[i])
{
Swap(&a[i-1],&a[i]);
size =1;
}
}
if (size==0)
{
break;
}
}
}
int main()
{
int a[N] = {0};
for (int i=0;i<N;++i)
{
a[i] = rand();
}
BubbleSort(a,N);
Print(a);
return 0;
}
其中有一段優(yōu)化程序,是定義一個(gè)變量判斷排序是否在做無(wú)效操作,當(dāng)內(nèi)循環(huán)處于交換狀態(tài)時(shí),則數(shù)據(jù)未排序完畢,否則視為,數(shù)據(jù)已有序,我們就可以break;中止掉程序,避免做無(wú)用遍歷。
1.3 冒泡最好的情況
待排序數(shù)列有序時(shí),時(shí)間復(fù)雜度是O(N)。外循環(huán)只執(zhí)行一次,內(nèi)循環(huán)N-1,N-2,N-3……
冒泡排序的特性總結(jié):
- 1. 冒泡排序是一種非常容易理解的排序
- 2. 時(shí)間復(fù)雜度:O(N^2)
- 3. 空間復(fù)雜度:O(1)
- 4. 穩(wěn)定
- 性:穩(wěn)定
總結(jié):
總的來(lái)說(shuō),冒泡排序是一種可以的排序,比直接選擇排序要好,雖然有優(yōu)化程序,但是,整體算法效率跟其他排序來(lái)比,還是差一些,比較適合新手學(xué)習(xí)。
?2.?交換排序——快速排序
快速排序(Quicksort)是Hoare于1962年提出的一種二叉樹結(jié)構(gòu)的交換排序方法,有時(shí)候也叫做劃分交換排序,是一個(gè)高效的算法,其基本思想為:任取待排序 元素序列中的某元素作為基準(zhǔn)值,按照該排序碼將待排序集合分割成兩子序列,左子序列中所有 元素均小于基準(zhǔn)值,右子序列中所有元素均大于基準(zhǔn)值,然后最左右子序列重復(fù)該過(guò)程,直到所 有元素都排列在相應(yīng)位置上為止。這是一個(gè)分治算法,而且它就在原地交換數(shù)據(jù)排序。
是目前已知最快的排序算法,會(huì)比一般的排序更節(jié)省時(shí)間。
2.1 快速排序——挖坑法
算法實(shí)現(xiàn):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
//打印
void Print(int *a,int n)
{
for (int i=0;i<n;++i)
{
printf("%d ",a[i]);
}
}
//挖坑法
void QuickSort(int* a,int left,int right)//升序
{
if (left < right)
{
int begin = left;
int end = right;
int pivot = begin;//記錄坑位的下標(biāo)
int key = a[begin];//坑值
while (begin < end)
{
//右邊找小,放到左邊
while (begin < end && a[end] >= key)//與坑值比較
{
--end;
}
//小的放在左邊的坑里,自己形成了新的坑位
a[pivot] = a[end];
pivot = end;
//左邊找大,放在右邊
while (begin < end && a[begin] <= key)//與坑值比較
{
++begin;
}
//大的放在右邊的坑里,自己形成了新的坑位
a[pivot] = a[begin];
pivot = begin;
}
//最后將坑值給到坑位
a[pivot] = key;
//[left,right]
//[left,pivot-1] [pivot+1,right]
//左子區(qū)間和右子區(qū)間有序,我們就有序了,如何讓他們有序?分治遞歸
QuickSort(a, left, pivot - 1);
QuickSort(a, pivot + 1, right);
}
else
{
return;
}
}
int main()
{
int a[10] = {0,9,5,6,3,2,1,7,8,4};
//挖坑法
QuickSort(a,0,sizeof(a)/sizeof(a[0])-1);
//打印
Print(a,sizeof(a) / sizeof(a[0]));
return 0;
}
快排的缺點(diǎn)
根據(jù)上面的代碼,我們來(lái)分析一下快排的缺點(diǎn):
如何解決快排對(duì)有序數(shù)據(jù)排序效率很差的方法?
三數(shù)取中法
所謂三數(shù)取中,不是取最大值,最小值,以及他們的中間值,而是取左邊(begin)、右邊(end)和中間(begin+end)/2;
在有序的情況下中間的值剛好就是二分,將取出的值作為坑位,就不會(huì)出現(xiàn)最差的這種情況。我們依舊使用區(qū)間的開頭作為“坑值”,但是要使用三數(shù)取中的邏輯。
選坑位:
int begin = left;
int end = right;
//使用三數(shù)取中選“坑值”,用mid存儲(chǔ)其下標(biāo)
int mid = GetMidIndex(a, begin, end);
//將區(qū)間首值當(dāng)作坑位
//坑值與首值交換,避免算法混亂
//一般我們會(huì)將區(qū)間首值作為坑值
Swap(&a[begin], &a[mid]);//傳地址調(diào)用
//存儲(chǔ)坑值
int key = a[begin];
三數(shù)取中 GetMidIndex();
int GetMidIndex(int *a,int left,int right)
{
//二分
int mid = (right - left) / 2;
if (a[left]<a[mid])
{
if (a[left]<a[right])
{
if (a[mid]<a[right])
{
return mid;
}
else //a[mid]>=a[right]
{
return right;
}
}
else //a[left]>=a[right]
{
return left;
}
}
else //a[left]>=a[mid]
{
if (a[mid]<a[right])
{
if (a[left]<a[right])
{
return left;
}
else //a[left]>=a[right]
{
return right;
}
}
else //a[mid]>=a[right]
{
return mid;
}
}
}
交換Swap();
//交換
void Swap(int* p1, int*p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
經(jīng)過(guò)三數(shù)取中的處理,就不會(huì)出現(xiàn)快排的最壞情況,但也幾乎不會(huì)成為最好的情況,有利有弊,我們?cè)诿嬖嚨倪^(guò)程中只需要寫基礎(chǔ)版的快排即可,以防時(shí)間不夠。
?小區(qū)間優(yōu)化:
關(guān)于如果處理數(shù)據(jù)多,相應(yīng)的遞歸次數(shù)多,會(huì)不會(huì)影響操作快排的性能?
當(dāng)我們?cè)谑褂每炫艑?duì)大量數(shù)據(jù)進(jìn)行排序時(shí),我們可以采用小區(qū)間優(yōu)化,減少遞歸次數(shù),達(dá)到優(yōu)化程序得到目的。
對(duì)當(dāng)待處理數(shù)據(jù)大于10的子序列進(jìn)行快排遞歸。
對(duì)當(dāng)待處理數(shù)據(jù)低于10的子序列進(jìn)行直接插入排序進(jìn)行排序,避免遞歸次數(shù)過(guò)多。
這個(gè)10不是固定的,可以根據(jù)處理的數(shù)據(jù)量調(diào)整。
//區(qū)間[left,right]
//左區(qū)間[left,pivot-1] 右區(qū)間[pivot+1,right]
//左子區(qū)間和右子區(qū)間有序,我們就有序了,如何讓他們有序?分治遞歸
// 小區(qū)間優(yōu)化
if (pivot - 1 - left > 10)//對(duì)當(dāng)待處理數(shù)據(jù)大于于10的子序列進(jìn)行快排遞歸排序
{
//快排
QuickSort(a,left,pivot-1);
}
else
{
//采用直接插入排序,對(duì)當(dāng)待處理數(shù)據(jù)低于10的子序列進(jìn)行排序,避免遞歸
InsertSort(a+left,pivot-1-left+1);//為什么最后要加1,例如:區(qū)間[0,9]實(shí)際上有10個(gè)數(shù)
}
if (right - (pivot + 1) > 10)
{
QuickSort(a,pivot+1,right);
}
else
{
InsertSort(a + pivot+1, right-(pivot+1)+1);
}
如果大家有想了解直接插入排序可以查看博主的另一篇:C語(yǔ)言常見排序算法之插入排序(直接插入排序,希爾排序)
2.3 快速排序——左右指針?lè)?/h3>
?根據(jù)上圖的示例我們應(yīng)該能夠理解左右指針?lè)ㄊ鞘裁礃拥倪壿嫞诳臃ㄊ且粯拥乃枷耄瑔翁伺判蛲戤厡?shí)現(xiàn)左邊比坑位小,右邊比坑位大。但是即使左右指針?lè)ǜ诳臃ǖ乃枷胧且粯拥模撬麄儐翁说倪\(yùn)算結(jié)果是不一樣的。
?算法實(shí)現(xiàn):
void QuickSort(int* a, int left, int right)
{
if (left < right)
{
int begin = left;
int end = right;
//選坑位
int mid = GetMidIndex(a, begin, end);//三數(shù)取中
Swap(&a[begin], &a[mid]);
int key = begin;
while (begin < end)
{
while (begin < end && a[end] <= a[key])
--end;
while (begin < end && a[begin] >= a[key])
++begin;
Swap(&a[begin], &a[end]);
}
Swap(&a[begin], &a[key]);
//分治遞歸
QuickSort(a, left, begin - 1);
QuickSort(a, begin + 1, right);
}
}
2.4 前后指針?lè)?/h3>
- 采用perv記錄區(qū)間第一個(gè)元素的下標(biāo),采用cur記錄區(qū)間第二個(gè)元素的下標(biāo)。
- cur找小,每次遇到比key(坑值)小的值就停下來(lái),++prev。
- 交換prev和cur位置的值
?算法實(shí)現(xiàn):
//左右指針?lè)?
void QuickSort(int* a, int left, int right)
{
if (left < right)
{
//選坑位
int mid = GetMidIndex(a, left,right);//三數(shù)取中
Swap(&a[left], &a[mid]);
int key = left;
//初始化指向
int prev = left, cur = left + 1;
while (cur<=right)
{
if (a[cur] <= a[key])//&&++prev!=cur
{
++prev;
//避免無(wú)效操作
if(cur!=prev)
Swap(&a[prev],&a[cur]);
}
++cur;
}
Swap(&a[key], &a[prev]);
//分治遞歸
QuickSort(a, left, prev - 1);
QuickSort(a, prev + 1, right);
}
}
快速排序的特性總結(jié):
- 1.快速排序整體的綜合性能和使用場(chǎng)景都是比較好的,所以才敢叫快速排序
- 2.時(shí)間復(fù)雜度:O(N*logN)
- 3.空間復(fù)雜度:O(logN)?
- 4.穩(wěn)定性:不穩(wěn)定
總結(jié):
快排是我們一定要掌握的一種排序算法,在面試、筆試中也是很常見的,博主分享的三種方法:挖坑法,左右指針?lè)ǎ昂笾羔樂(lè)ǎ簧僖莆找环N,但是要其他的方法也要知道算法思想。還有兩種優(yōu)化方式,小區(qū)間優(yōu)化和三數(shù)取中,也要知道是什么邏輯,解決什么問(wèn)題。
原文鏈接:https://blog.csdn.net/weixin_67603503/article/details/125487641
相關(guān)推薦
- 2022-11-08 詳解Python中數(shù)據(jù)處理的方法總結(jié)及實(shí)現(xiàn)_python
- 2022-12-03 React為什么需要Scheduler調(diào)度器原理詳解_React
- 2022-01-29 寶塔Linux面板的ftp無(wú)法使用連接解決方案
- 2022-08-05 python內(nèi)置模塊之上下文管理contextlib_python
- 2022-07-22 mybatis源碼之集成springboot原理
- 2022-12-01 C++中高性能內(nèi)存池的實(shí)現(xiàn)詳解_C 語(yǔ)言
- 2022-01-14 函數(shù)的防抖和節(jié)流&&深淺克隆
- 2023-04-17 淺談Golang數(shù)據(jù)競(jìng)態(tài)_Golang
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支