【數據結構初階】--排序(三):冒泡排序、快速排序

😘個人主頁:@Cx330?

👀個人簡介:一個正在努力奮斗逆天改命的二本覺悟生

📖個人專欄:《C語言》《LeetCode刷題集》《數據結構-初階》

前言:在上篇博客的學習中,我們掌握了直接選擇排序和堆排序,發現堆排序算法的優秀,那么今天我們就進入冒泡排序和快速排序的學習中


目錄

一、冒泡排序

代碼實現:

測試結果:

復雜度分析:

二.快速排序(遞歸實現)

代碼實現(框架):

找基準值劃分的三種實現方式:?

1.hoare版本

2.挖坑法

3.lomuto前后指針

4.基準值的重要性?

時間復雜度:

測試結果:

三.非遞歸實現快速排序

代碼實現:

測試結果:

四.冒泡排序和快速排序性能對比

代碼實現:


一、冒泡排序

冒泡排序大家肯定都不陌生了,我們直接來看代碼吧

代碼實現:

//冒泡排序
void BubbleSort(int* arr, int n)
{int change = 1;for (int i = 0; i < n-1; i++){for (int j = 0; j < n - 1 - i; j++){if (arr[j] > arr[j + 1]){Swap(&arr[j], &arr[j + 1]);change = 0;}}if (change == 1){break;}}
}

圖解:

test.c:

#include"Sort.h"
void printArr(int* arr, int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}void test01()
{int a[] = { 5,3,9,6,2,4,7,1,8 };//int a[] = { 6,1,2,7,9,3 };int n = sizeof(a) / sizeof(a[0]);printf("排序之前:");printArr(a, n);//InsertSort(a, n);//ShellSort(a, n);//SelectSort(a, n);//HeapSort(a, n);BubbleSort(a, n);//QuickSort(a, 0, n - 1);//QuickSortNorR(a, 0, n - 1);//MergeSort(a, n);//CountSort(a, n);//MergeSortNonR(a, n);printf("排序之后:");printArr(a, n);
}
int main()
{test01();return 0;
}

測試結果:

測試完成,打印沒有問題,升序排列正確,退出碼為0

復雜度分析:

時間復雜度:O(N^2)

冒泡排序比較簡單,博主這里的冒泡排序是優化過的,當某一次已經有序了就會直接結束


二.快速排序(遞歸實現)

快速排序是Hoare于1962年提出的?種二叉樹結構的交換排序方法,其基本思想為:任取待排序元素序列中的某元素作為基準值,按照該排序碼將待排序集合分割成兩個子序列,左子序列中所有元素均小于基準值,右子序列中所有元素均大于基準值,然后最左右子序列重復該過程,直到所有元素都排列在相應位置上為止。

代碼實現(框架):

void QuickSort(int* arr, int left, int right)
{if (left >= right){return ;}//左【left, keyi - 1】   右【keyi+1,right】int keyi = _QuickSort(arr, left, right);QuickSort(arr, left, keyi - 1);QuickSort(arr,keyi+1,right);
}

圖解:

采用遞歸的思想,有些類似二叉樹的前序遍歷,分組進行劃分。

找基準值劃分的三種實現方式:?

將區間元素進行劃分的_QuickSort方法主要有以下幾種實現方法:

1.hoare版本

思路:

  1. 創建left和right指針
  2. (升序)從右向左找出比基準值小的數據,從左向右找出比基準值大的數據,左右指針數據交換。進入下次循環-------我們默認left初始位置的值為基準值,特別注意一下確定基準之后left要先++一次。
  3. 循環結束后,交換keyi和right位置的值,然后返回right下標

代碼實現:

//找基準值--hoare版本
int _QuickSort1(int* arr, int left, int right)
{int keyi = left;left++;while (left <= right){//right從右往左找比基準值小的,如果大于基準值就--while (left <= right && arr[right] > arr[keyi]){--right;}//left從左往右找比基準值大的,如果小于基準值就++while (left <= right && arr[left] < arr[keyi]){++left;}//left和right交換if(left<=right)Swap(&arr[left++], &arr[right--]);}//right位置就是基準值的位置Swap(&arr[right], &arr[keyi]);return right;
}

注意事項:

1.為什么跳出循環后right位置的值一定不大于key?

答:當left>right時,即right走到left的左側,而left掃過的數據均不大于key,所有right此時所指向的數據一定不大于key

三種情況都在圖中有所講解,大家可以仔細的看一下

2.內層循環不能等于,數據重復的情況下會造成時間復雜度變為O(n^2)

2.挖坑法

思路:

  • 創建右指針,(這個思路left不用先++)首先從右向左找出比基準小的,找到后立即放入坑中,當前位置變為新的坑。
  • 然后從左到右找出比基準大的數據,找到后立即放到右邊坑中,當前位置變為新的坑
  • 結束循環后將最開始存儲的分界值放入當前的坑中,返回當前坑的下標

代碼實現:

//找基準值--挖坑法
int _QuickSort(int* arr, int left, int right)
{int hole = left;int key = arr[hole];while (left < right){while (left<right && arr[right] > key){right--;}arr[hole] = arr[right];hole = right;while (left < right && arr[left] < key){++left;}arr[hole] = arr[left];hole = left;}arr[hole] = key;return hole;
}

圖解:

3.lomuto前后指針

思路:

  1. 創建左右指針prev和cur,cur從左往右找比基準值要小的數據
  2. cur指向的數據比基準值要小的話,++prev,(且++prev!=cur),交換cur和prev位置的值。再++cur
  3. cur指向的數據不比基準值小的話,直接++cur
  4. 循環結束后,交換keyi位置和prev位置的值,返回prev下標

代碼實現:

//找基準值--lumoto雙指針法
int _QuickSort2(int* arr, int left, int right)
{int prev = left, cur = prev + 1;int keyi = left;while (cur < right){if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[cur], &arr[prev]);}++cur;}Swap(&arr[prev], &arr[keyi]);return prev;
}

圖解:

4.基準值的重要性?

我們一般默認最左邊就是我們要找的基準值,但其實我們是有特定方法求出比較好的基準值的,這個在后續的提升篇中會有講解?

時間復雜度:

O(n*logn)

遞歸時間復雜度=單次遞歸時間復雜度(n)*遞歸次數(樹的最大高度,logn)=n*logn

test.c:

#include"Sort.h"void PrintArr(int* arr, int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}void test1()
{int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };int size = sizeof(a) / sizeof(a[0]);printf("排序前:");PrintArr(a, size);//快速排序QuickSort(a, 0, size - 1);printf("排序后:");PrintArr(a, size);
}int main()
{test1();return 0;
}

測試結果:

三種找基準的方法結合快速排序主體最后測試都沒有問題,這里就放一個圖了,升序排列正確,退出碼為0


三.非遞歸實現快速排序

我們非遞歸實現快速排序需要借助數據結構棧,直接使用我們之前自己實現過的就行了,然后在.c文件里面引入一下棧的.h文件。具體操作和之前我們借助隊列實現二叉樹的層序遍歷和判斷二叉樹是否為完全二叉樹差不多,但是不用修改數據類型

思路:

1.初始化棧:首先將整個數組的起始索引(如 0)和結束索引(如 n-1)作為初始區間推入棧中,先推結束的再推起始的。

2.循環處理棧中區間:當棧不為空時,執行以下操作:

  • 彈出區間:從棧頂取出一個待排序區間的起始索引 begin?和結束索引 end。
  • 劃分區間:調用劃分函數(與遞歸版相同),選擇基準元素,將區間 [begin, end] 劃分為左子區間 [begin, keyi-1](元素均小于基準)和右子區間 [keyi+1, end](元素均大于基準),返回基準元素的最終位置 keyi。
  • 推入子區間:若左子區間存在(即 begin?< keyi-1),將其邊界 (begin, keyi-1) 推入棧;若右子區間存在(即 keyi+1 < end),將其邊界 (keyi+1, end) 推入棧。

3.結束條件:當棧為空時,所有子區間均已排序完成,整個數組排序結束。

需要注意的時棧先進后出,所以我們一般都是先推右再推左,不管是左子區間右子區間還是左邊范圍右邊范圍都是這樣的

代碼實現:

//找基準值非遞歸版--棧
int QuickSortNorR(int* arr, int left, int right)
{ST st;STInit(&st);STPush(&st, right);STPush(&st, left);while (!STEmpty(&st)){//取棧頂兩次int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);//[begin,end]--找基準值int keyi = begin;int prev = begin, cur = prev + 1;while (cur <= end){if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[cur], &arr[prev]);}++cur;}Swap(&arr[prev], &arr[keyi]);keyi = prev;if (keyi + 1 < end){STPush(&st, end);STPush(&st, keyi+1);}if (begin < keyi - 1){STPush(&st, keyi - 1);STPush(&st, begin);}}STDesTroy(&st);
}

圖解:

test.c:

#include"Sort.h"void PrintArr(int* arr, int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}void test1()
{int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };int size = sizeof(a) / sizeof(a[0]);printf("排序前:");PrintArr(a, size);//非遞歸快速排序QuickSortNoare(a, 0, size - 1);printf("排序后:");PrintArr(a, size);
}
int main()
{test1();return 0;
}

測試結果:

測試完成,打印沒有問題,非遞歸版本升序排序正確,退出碼為0


四.冒泡排序和快速排序性能對比

我們還是通過測試來對比一下這兩種排序的性能,大家也可以看看和之前實現過的排序的對比

代碼實現:

#include"Sort.h"void PrintArr(int* arr, int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}void test1()
{int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };int size = sizeof(a) / sizeof(a[0]);printf("排序前:");PrintArr(a, size);//直接插入排序//InsertSort(a, size);//希爾排序//ShellSort(a, size);//直接選擇排序//SelectSort(a, size);//堆排序//HeapSort(a, size);//冒泡排序//BubbleSort(a, size);//快速排序//QuickSort(a, 0, size - 1);//非遞歸快速排序QuickSortNoare(a, 0, size - 1);printf("排序后:");PrintArr(a, size);
}// 測試排序的性能對比
void TestOP()
{srand(time(0));const int N = 100000;int* a1 = (int*)malloc(sizeof(int) * N);int* a2 = (int*)malloc(sizeof(int) * N);int* a3 = (int*)malloc(sizeof(int) * N);int* a4 = (int*)malloc(sizeof(int) * N);int* a5 = (int*)malloc(sizeof(int) * N);int* a6 = (int*)malloc(sizeof(int) * N);int* a7 = (int*)malloc(sizeof(int) * N);for (int i = 0; i < N; ++i){a1[i] = rand();a2[i] = a1[i];a3[i] = a1[i];a4[i] = a1[i];a5[i] = a1[i];a6[i] = a1[i];a7[i] = a1[i];}int begin1 = clock();InsertSort(a1, N);int end1 = clock();int begin2 = clock();ShellSort(a2, N);int end2 = clock();int begin3 = clock();SelectSort(a3, N);int end3 = clock();int begin4 = clock();HeapSort(a4, N);int end4 = clock();int begin5 = clock();QuickSort(a5, 0, N - 1);int end5 = clock();int begin6 = clock();//MergeSort(a6, N);int end6 = clock();int begin7 = clock();BubbleSort(a7, N);int end7 = clock();printf("InsertSort:%d\n", end1 - begin1);printf("ShellSort:%d\n", end2 - begin2);printf("SelectSort:%d\n", end3 - begin3);printf("HeapSort:%d\n", end4 - begin4);printf("QuickSort:%d\n", end5 - begin5);//printf("MergeSort:%d\n", end6 - begin6);printf("BubbleSort:%d\n", end7 - begin7);free(a1);free(a2);free(a3);free(a4);free(a5);//free(a6);free(a7);
}int main()
{TestOP();//test1();return 0;
}

我們可以看出快速排序要比冒泡快很多,和其它排序相比的情況下,快速排序也是很快的


往期回顧:

【數據結構初階】--排序(一):直接插入排序,希爾排序

【數據結構初階】--排序(二):直接選擇排序,堆排序

總結:本篇博客就到此結束了,主要實現了一下兩種交換排序,一個冒泡排序,一個快速排序。我們通過對比可知快速排序優于冒泡排序。其中快速排序找基準值我們提供了三種方法,如果文章對你有幫助的話,歡迎評論,點贊,收藏加關注,感謝大家的支持。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/918878.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/918878.shtml
英文地址,請注明出處:http://en.pswp.cn/news/918878.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

名詞概念:什么是尾部誤差?

“尾部誤差”就是指誤差分布在兩端的那一小撮、但數值特別大的誤差——也就是離中心&#xff08;均值/中位數&#xff09;很遠的“極端樣本”的誤差。對應統計學里的“分布尾部”&#xff08;tails&#xff09;。通俗點&#xff1a;大多數樣本誤差都很小&#xff0c;但總會有少…

記對外國某服務器的內網滲透

本專欄是筆者的網絡安全學習筆記&#xff0c;一面分享&#xff0c;同時作為筆記 文章目錄前文鏈接前言上線CS上線rdp后滲透信息收集SMB Pth攻擊權限維持魔幻上線提權關Windows Defenderend前文鏈接 WAMP/DVWA/sqli-labs 搭建burpsuite工具抓包及Intruder暴力破解的使用目錄掃描…

速賣通平臺關鍵字搜索商品列表列表接口實現指南:從接口分析到代碼落地

在跨境電商開發中&#xff0c;速賣通平臺的商品數據獲取是許多開發者關注的焦點。本文將詳細介紹如何實現速賣通關鍵字搜索商品列表接口&#xff0c;涵蓋接口請求參數分析、簽名機制、分頁處理及完整代碼實現&#xff0c;幫助開發者快速對接速賣通開放平臺。一、接口基本信息速…

UE UDP通信

1.確保工程為C工程&#xff0c;在項目工程的xx.Build.cs中加入Networking和Sockets模塊。PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Networking", "Socke…

JavaScript 邏輯運算符與實戰案例:從原理到落地

JavaScript 中的邏輯運算符不僅是條件判斷的核心&#xff0c;還能通過“短路特性”簡化代碼&#xff1b;結合 DOM 操作的實戰案例&#xff0c;更能體現其靈活性。本文整理了邏輯運算符的個人理解、優先級規則&#xff0c;以及 4 個高頻實戰需求的實現方案&#xff0c;附個人思路…

Android RxJava 過濾與條件操作詳解

RxJava 是一個基于觀察者模式的響應式編程庫&#xff0c;在 Android 開發中被廣泛使用。其中&#xff0c;過濾和條件操作是 RxJava 中非常重要的一部分&#xff0c;它們允許我們對數據流進行精細控制。本文將詳細介紹 RxJava 中常用的過濾與條件操作符及其使用場景。一、過濾操…

云手機都具有哪些特點?

云手機擁有著便捷的遠程操作功能&#xff0c;讓用戶無論身處何地&#xff0c;只要能連接網絡&#xff0c;就能通過手機、電腦等終端設備遠程操控云手機&#xff0c;無需受限于物理位置&#xff0c;大大提升了工作的靈活性與便捷性。云手機主要是依賴于云計算技術&#xff0c;能…

Sparse-ICP—(4) 加權稀疏迭代最近點算法(matlab版)

目錄 一、算法原理 1、原理概述 2、參考文獻 二、代碼實現 三、結果展示 一、算法原理 1、原理概述 見:Sparse-ICP—(1)稀疏迭代最近點算法 2、參考文獻 二、代碼實現 SparseWeightedDistance.m function [move_points,T] =

統信UOS安裝NFS共享文件夾

在 UOS ARM 架構系統上安裝和配置 NFS 服務&#xff0c;實現與局域網中其他服務器共享文件夾的步驟如下&#xff1a;1. 安裝 NFS 服務首先更新系統并安裝 NFS 服務器組件&#xff1a;bash# 更新軟件包列表 sudo apt update# 安裝NFS服務器 sudo apt install nfs-kernel-server …

【完整源碼+數據集+部署教程】孔洞檢測系統源碼和數據集:改進yolo11-RetBlock

背景意義 研究背景與意義 隨著工業自動化和智能制造的快速發展&#xff0c;孔洞檢測作為關鍵的質量控制環節&#xff0c;受到了廣泛關注。孔洞的存在可能會影響產品的強度、密封性和整體性能&#xff0c;因此&#xff0c;準確、快速地檢測孔洞對于保障產品質量至關重要。傳統的…

k8s環境使用Operator部署Seaweedfs集群(一)

#作者&#xff1a;閆乾苓 文章目錄4.1 前置條件4.2 部署seaweedfs-operator4.3 準備operator鏡像SeaweedFS Operator是一個Kubernetes Operator&#xff0c;用于自動化部署和管理SeaweedFS集群 README.md:6-8 。部署分為兩個階段&#xff1a;首先部署Operator本身&#xff0c;然…

實踐基地落地:成都影像產業園與重慶五一職院強實訓

近日&#xff0c;成都國際影像產業園與重慶五一職業技術學院合作的實踐基地正式落地&#xff0c;這一舉措為雙方強化實訓合作、培養高素質技能人才注入了新的活力。實踐基地的落地&#xff0c;是雙方基于各自優勢資源的深度融合。成都國際影像產業園作為影像行業的重要聚集地&a…

算法----滑動窗口

滑動窗口 什么是滑動窗口 滑動窗口是一種常用的技術&#xff0c;主要用于處理連續數據序列&#xff08;如數組、字符串或時間序列數據&#xff09;&#xff0c;通過動態調整一個固定大小的“窗口”來高效地解決問題。窗口在序列上“滑動”&#xff0c;每次移動一個位置&#xf…

Rust學習筆記(三)|所有權機制 Ownership

本篇文章包含的內容1 重新從堆和棧開始考慮2 所有權規則3 變量和數據&#xff08;值&#xff09;的交互方式3.1 移動 Move3.2 克隆 Clone3.3 復制 Copy4 函數與所有權4.1 參數傳遞時的所有權轉移4.2 函數返回時的所有權轉移5 引用和借用6 切片前面兩篇僅僅介紹了一些Rust的語法…

Redis 知識點與應用場景

1. Redis 簡介與核心特性Redis&#xff08;Remote Dictionary Server&#xff09;是一款開源的內存數據存儲系統&#xff0c;支持多種數據結構&#xff0c;兼具高性能、持久化、分布式等特性&#xff0c;廣泛用于緩存、數據庫、消息中間件等場景。其核心特性包括&#xff1a;高…

日常反思總結

1.group by和order by的區別

易貝 (eBay (eBay) 關鍵字搜索 API 實戰:從認證到商品列表獲取全流程解析

在跨境電商開發領域&#xff0c;eBay 作為全球最大的在線交易平臺之一&#xff0c;其開放 API 為開發者提供了豐富的商品數據獲取能力。本文將聚焦 eBay 關鍵字搜索商品列表接口的實現&#xff0c;涵蓋 OAuth2.0 認證、高級搜索參數配置、分頁策略及完整代碼實現&#xff0c;幫…

敏捷數據開發實踐:基于 Amazon Q Developer + Remote MCP 構建本地與云端 Amazon Redshift 交互體系

敏捷數據開發實踐&#xff1a;基于 Amazon Q Developer Remote MCP 構建本地與云端 Amazon Redshift 交互體系 新用戶可獲得高達 200 美元的服務抵扣金 亞馬遜云科技新用戶可以免費使用亞馬遜云科技免費套餐&#xff08;Amazon Free Tier&#xff09;。注冊即可獲得 100 美元的…

【SpringBoot】11 概念理解 - 深入理解 Java 和 Spring 中的容器、組件、類、對象與 Bean

文章目錄引言1. 基本概念解析1.1 類&#xff08;Class&#xff09;1.2 對象&#xff08;Object&#xff09;1.3 組件&#xff08;Component&#xff09;1.4 Bean 實例&#xff08;Bean Instance&#xff09;1.5 容器&#xff08;Container&#xff09;2. 運行時 vs. 非運行時的…

【學習嵌入式day-25-線程】

exec函數族exec函數族利用進程空間執行另一份代碼#include "../head.h"int main(void) {char *parg[5] {"./hello","how","are","you",NULL,};printf("execl-up\n");//execl("./hello", "./hello…