快速排序(Quick Sort)算法詳解(遞歸與非遞歸)

引言

在計算機科學中,排序算法是最基礎且重要的算法之一。快速排序(Quick Sort)作為一種高效的排序算法,在實際應用中被廣泛使用。平均時間復雜度為?(O(n log n)),最壞情況下為?(O(n^2))。本文將詳細介紹快速排序算法的原理,結合具體代碼進行分析,給出兩種遞歸方法與一種非遞歸方法,并給出兩種優化方案。

快速排序原理

快速排序的基本思想是通過選擇一個基準元素(key),將數組分為兩部分,使得左邊部分的所有元素都小于等于基準元素,右邊部分的所有元素都大于等于基準元素。然后遞歸地對左右兩部分進行排序,最終得到一個有序的數組。

具體步驟如下:

  1. 選擇基準元素:從數組中選擇一個元素作為基準元素。
  2. 分區操作:將數組中的元素重新排列,使得所有小于等于基準元素的元素都在基準元素的左邊,所有大于等于基準元素的元素都在基準元素的右邊。這個過程稱為分區(partition)。
  3. 遞歸排序:對左右兩部分分別遞歸地應用快速排序算法。

代碼實現

1.hoare法(遞歸)

/交換
void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
//快速排序
void QuickSort(int* a, int left, int right)
{if(left >= right)return;int key = left;int begin = left, end = right;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[key], &a[begin]);key = begin;QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);}
}

?2.雙指針法(遞歸)

//快排遞歸:雙指針
int PartSort2(int* a, int left, int right)
{//三數取中(優化)int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int key = left;int prev = left;int cur = prev + 1;while(cur <= right){if(a[cur] < a[key] && ++prev != cur){Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[prev], &a[key]);return prev;
}
//快速排序(遞歸)
void QuickSort(int* a, int left, int right)
{if(left >= right)return;//小區間優化if((right - left + 1) < 10){InsertSort(a + left, right - left + 1);}else{// int key = PartSort1(a, left, right);int key = PartSort2(a, left, right);QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);}
}

代碼解釋:
  1. Swap?函數:用于交換兩個整數的值。
  2. QuickSort?函數
    • 遞歸終止條件:當?left >= right?時,說明數組已經有序,直接返回。
    • 分區操作:使用雙指針法將數組分為兩部分,左邊部分的元素都小于等于基準元素,右邊部分的元素都大于等于基準元素。
    • 遞歸排序:對左右兩部分分別遞歸地調用?QuickSort?函數。

3.非遞歸法

遞歸方法來實現排序終歸有棧溢出的風險,所以這里提供一種非遞歸方式實現快速排序。

先給定棧的實現

//Stack.h
#pragma once#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>typedef int STDataType;typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;//初始化與銷毀
void STInit(ST* pst);
void STDestory(ST* pst);//入棧 出棧
void STPush(ST* pst, STDataType x);
void STPop(ST* pst);//取棧頂數據
STDataType STTop(ST* pst);//判空
bool STEmpty(ST* pst);//獲取數據的個數
int STSize(ST* pst);
//Stack.c
#include "Stack.h"//初始化與銷毀
void STInit(ST* pst)
{assert(pst);pst->a = NULL;pst->top = 0;pst->capacity = 0;
}
void STDestory(ST* pst)
{assert(pst);free(pst->a);pst->a = NULL;pst->top = pst->capacity = 0;
}//入棧 出棧
void STPush(ST* pst, STDataType x)
{assert(pst);//擴容if(pst->top == pst->capacity){int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity* sizeof(STDataType));if(tmp == NULL){perror("realloc fail");exit(1);}pst->a = tmp;pst->capacity = newcapacity;}    //入棧pst->a[pst->top] = x;pst->top++;}
void STPop(ST* pst)
{assert(pst);assert(pst->top > 0);pst->top--;
}//取棧頂數據
STDataType STTop(ST* pst)
{assert(pst);assert(pst->top > 0);return pst->a[pst->top - 1];
}//判空
bool STEmpty(ST* pst)
{assert(pst);return pst->top == 0;
}//獲取數據的個數
int STSize(ST* pst)
{assert(pst);return pst->top;
}

?運用棧來實現非遞歸快排:

//快速排序(非遞歸)
void QuickSortNonR(int* a, 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);int key = PartSort2(a, begin, end);if(end > key + 1){STPush(&st, end);STPush(&st, key + 1);}if(begin < key - 1){STPush(&st, key - 1);STPush(&st, begin);}}STDestory(&st);
}

這里使用棧來存儲指針begin與end?,棧在堆中存儲,能通過malloc不斷申請內存空間,有效規避了棧溢出的風險。

測試代碼

下面對快速排序做一個簡單的測試:

void TestOP()
{srand((unsigned int)time(NULL));const int N = 100000;int* a1 = (int*)malloc(sizeof(int) * N);for(int i = 0; i < N; i++){a1[i] = rand();}int begin1 = clock();// 此處可調用 QuickSort 進行測試QuickSort(a1, 0, N - 1);int end1 = clock();printf("QuickSort:%d\n", end1 - begin1);
}int main()
{TestOP();return 0;
}

在這個測試文件中,我們生成了一個包含 100000 個隨機整數的數組,并調用?QuickSort?函數對其進行排序,最后輸出排序所需的時間。

復雜度分析

  • 時間復雜度:平均情況下為?(O(n log n)),最壞情況下為?(O(n^2))。
  • 空間復雜度:遞歸調用棧的深度為?(O(log n)),因此空間復雜度為?(O(log n))。

優化方案?

1.三數取中

若數組排序前就有序,時間復雜度為O(n^2),那么此時三數取中就是消除這種接近有序帶來的大量重復計算的優化方法

代碼實現

//三數取中
int GetMid(int* a, int left, int right)
{int mid = (left + right) / 2;// left midi rightif (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]{if (a[mid] > a[right]){return mid;}else if (a[left] < a[right]){return left;}else{return right;}}
}

緊接著再把返回的mid值與left處值交換即可

//三數取中(優化)int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);

2.小區間優化

我們通過前面所學內容可以得知快排的遞歸是類似與二叉樹遞歸的一種算法,那么高度越高所消耗的空間越大,每個遞歸函數的調用會建立一個函數棧幀,為了避免出現棧溢出的情況,我們可以采取小區間優化來規避風險并高效實現排序。

代碼實現

(可任選一種時間復雜度為O(n^2)的排序,這里選擇更為高效的插入排序。)

//插入排序 最好O(N) 最壞O(N ^ 2)
void InsertSort(int* a, int n)
{for(int i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];while(end >= 0){if(tmp < a[end]){a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;}
}
//小區間優化if((right - left + 1) < 10){InsertSort(a + left, right - left + 1);}

3.其他

除了這些優化方案,可能有人會覺得快速排序難以理解,這里還有一種較為通俗的挖坑法(并沒有改變排序效率,只是更為通俗易懂),可以自行查閱資料。

總結

快速排序是一種高效的排序算法,通過分治法和分區操作,將數組不斷地分為兩部分進行排序。在實際應用中,通過三數取中和小區間優化,可以提高算法的性能。希望本文對你理解快速排序算法有所幫助。

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

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

相關文章

修改 vscode 左側導航欄的文字大小 (更新版)

新增, 個人常用 按 Ctrl Shift P 打開命令面板 輸入并選擇 : Developer: Toggle Developer Tools 打開開發者工具。 1. 起因&#xff0c; 目的: 問題&#xff1a; vscode 左側的文字太小了&#xff01;&#xff01;&#xff01;我最火的一篇文章&#xff0c;寫的就是這個…

Kerberos面試內容整理-Kerberos 的配置與排障

正確配置 Kerberos 對其正常工作至關重要。在Linux/Unix環境下,Kerberos配置通常通過編輯配置文件(例如 /etc/krb5.conf)完成。其中指定了Realm名稱、KDC和管理員服務器地址、默認域到Realm的映射等參數。管理員需要在KDC端初始化數據庫并創建主體(可以使用 kadmin 等工具添…

Windows + CPU也能跑時序預測:TSLib框架快速上手與踩坑避雷

在時序預測領域,選擇一個成熟的框架往往能讓我們事半功倍。最近接手了一個緊急的時序預測項目,經過一番調研后,我選擇了TSLib(Time-Series-Library)這個優秀的開源框架來快速搭建整個預測流程。 由于開發環境限制在Windows平臺且沒有GPU支持,整個部署過程還是遇到了一些…

從 0 到 1:用 Trae 插件 Builder 模式開發端午包粽子小游戲

? 前言 Trae插件獲取&#xff1a;https://www.trae.com.cn/plugin 在編程的世界里&#xff0c;效率就是生命。我們開發者常常為了一個項目的搭建&#xff0c;重復著創建文件夾、初始化項目配置、編寫樣板代碼等一系列繁瑣的操作&#xff0c;耗費了大量的時間和精力。而如今…

React-native之Flexbox

本文總結: 我們學到了 React Native 的 Flexbox 布局&#xff0c;它讓寫樣式變得更方便啦&#xff01;&#x1f60a; Flexbox 就像一個有彈性的盒子&#xff0c;有主軸和交叉軸&#xff08;行或列&#xff09;。 在 RN 里寫樣式要用 StyleSheet.create 對象&#xff0c;屬性名…

Leetcode 1336. 每次訪問的交易次數

1.題目基本信息 1.1.題目描述 表: Visits ---------------------- | Column Name | Type | ---------------------- | user_id | int | | visit_date | date | ---------------------- (user_id, visit_date) 是該表的主鍵(具有唯一值的列的組合) 該表的每行表示 use…

騰訊云國際版和國內版賬戶通用嗎?一樣嗎?為什么?

在當今全球化的數字化時代&#xff0c;云計算服務成為眾多企業和個人拓展業務、存儲數據的重要選擇。騰訊云作為國內領先的云服務提供商&#xff0c;其國際版和國內版備受關注。那么&#xff0c;騰訊云國際版和國內版賬戶是否通用&#xff1f;它們究竟一樣嗎&#xff1f;背后又…

解鎖Java多級緩存:性能飛升的秘密武器

一、引言 文末有彩蛋 在當今高并發、低延遲的應用場景中&#xff0c;傳統的單級緩存策略往往難以滿足性能需求。隨著系統規模擴大&#xff0c;數據訪問的瓶頸逐漸顯現&#xff0c;如何高效管理緩存成為開發者面臨的重大挑戰。多級緩存架構應運而生&#xff0c;通過分層緩存設…

Android Kotlin 算法詳解:鏈表相關

前言 &#x1f60a; 在 Android 開發中&#xff0c;算法與數據結構是基本功之一&#xff0c;而鏈表&#xff08;Linked List&#xff09;作為常見的數據結構&#xff0c;經常出現在各類面試題與實際業務場景中。本文將以 Android Kotlin 為語言&#xff0c;結合 LeetCode 上的…

Blinko智能筆記系統實現跨平臺同步與隱私保護的完整技術方案解析

文章目錄 前言1. Docker Compose一鍵安裝2. 簡單使用演示3. 安裝cpolar內網穿透4. 配置公網地址5. 配置固定公網地址 推薦 ? 前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。 點擊跳轉到網站 前言 是否…

【小紅書】API接口,獲取筆記列表

小紅書筆記列表API接口詳解 - 深圳小于科技助力高效數據對接 深圳小于科技&#xff08;官網&#xff1a;https://www.szlessthan.com&#xff09;提供的小紅書筆記列表API接口&#xff0c;幫助開發者快速獲取小紅書平臺筆記數據&#xff0c;實現高效內容管理與分析。 接口概述…

Qwen2.5-VL 損失函數

Qwen2.5-VL 損失函數 flyfish 文章名稱鏈接深入理解交叉熵損失 CrossEntropyLoss - 概率基礎鏈接深入理解交叉熵損失 CrossEntropyLoss - 對數鏈接深入理解交叉熵損失 CrossEntropyLoss - 概率分布鏈接深入理解交叉熵損失 CrossEntropyLoss - 信息論&#xff08;交叉熵&#…

LeetCode 1524. 和為奇數的子數組數目

好的&#xff01;讓我們詳細解釋 LeetCode 1524. 和為奇數的子數組數目 這道題的思路和解法。 題目&#xff1a; https://leetcode.cn/problems/number-of-sub-arrays-with-odd-sum/description/ 題目分析 問題描述&#xff1a; 給定一個整數數組 arr&#xff0c;返回其中和…

kafka學習筆記(三、消費者Consumer使用教程——消費性能多線程提升思考)

1.簡介 KafkaConsumer是非線程安全的&#xff0c;它定義了一個acquire()方法來檢測當前是否只有一個線程在操作&#xff0c;如不是則會拋出ConcurrentModifcationException異常。 acquire()可以看做是一個輕量級鎖&#xff0c;它僅通過線程操作計數標記的方式來檢測線程是否發…

百度蜘蛛池的作用是什么?技術@baidutopseo

百度蜘蛛池的作用 百度蜘蛛池是一種用于集中管理和調度百度搜索引擎蜘蛛&#xff08;爬蟲&#xff09;的技術工具。其主要目的是優化網站內容的收錄效率&#xff0c;幫助搜索引擎更高效地發現和抓取網頁內容。技術baidutopseo 提升收錄效率 通過集中大量百度蜘蛛的訪問請求&…

黑馬Java面試筆記之 消息中間件篇(Kafka)

一. Kafka保證消息不丟失 Kafka如何保證消息不丟失 使用Kafka在消息的收發過程中都會出現消息丟失&#xff0c;Kafka分別給出了解決方案 生產者發送消息到Brocker丟失消息在Brocker中存儲丟失消費者從Brocker接收消息丟失 1.1 生產者發送消息到Brocker丟失 設置異步發送 消息…

dis css port brief 命令詳細解釋

華為交換機命令 display css port brief 詳細解釋 display css port brief 是華為交換機中用于 快速查看堆疊&#xff08;CSS&#xff0c;Cluster Switch System&#xff09;端口狀態及關鍵參數 的命令&#xff0c;適用于日常運維、堆疊鏈路健康檢查及故障定位。以下是該命令的…

Redis 緩存問題及其解決方案

1. 緩存雪崩 概念&#xff1a;緩存雪崩是指在緩存層出現大范圍緩存失效或緩存服務器宕機的情況下&#xff0c;大量請求直接打到數據庫&#xff0c;導致數據庫壓力驟增&#xff0c;甚至可能引發數據庫宕機。 影響&#xff1a;緩存雪崩會導致系統性能急劇下降&#xff0c;甚至導…

使用Python進行函數作畫

前言 因為之前通過deepseek繪制一下卡通的人物根本就不像&#xff0c;又想起來之前又大佬通過函數繪制了一些圖像&#xff0c;想著能不能用Python來實現&#xff0c;結果發現可以&#xff0c;不過一些細節還是需要自己調整&#xff0c;deepseek整體的框架是沒有問題&#xff0…

關于list集合排序的常見方法

目錄 1、list.sort() 2、Collections.sort() 3、Stream.sorted() 4、進階排序技巧 4.1 空值安全處理 4.2 多字段組合排序 4.3. 逆序 5、性能優化建議 5.1 并行流加速 5.2 原地排序 6、最佳實踐 7、注意事項 前言 Java中對于集合的排序操作&#xff0c;分別為list.s…