日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

算法与数据结构(归并排序)

發布時間:2025/3/21 编程问答 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 算法与数据结构(归并排序) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

歸并排序 Merge Sort

利用分治策略來解決排序問題

分治法 Divide and Conquer:

分治法的基本思想如下:

  • 分解:將原問題分解為若干子問題,這些子問題是原問題的規模較小的實例。
  • 解決:遞歸地求解這些子問題。當子問題規模足夠小時,可直接求解。
  • 合并:合并子問題的解成原問題的解。

歸并排序算法完全遵循分治模式。其操作步驟如下:

  • 分解:將原序列分解為兩個各占 n/2n/2n/2 個元素的子序列。
  • 解決:使用歸并排序遞歸地排序兩個子序列。這里的策略是不斷分解直到子序列只包含一個元素,此時子序列必然有序。
  • 合并:合并兩個已排序的子序列以產生有序序列。

歸并排序的思路:

如上所述,歸并排序的基本思路是先將序列分為兩部分,讓左右兩部分分別有序,然后合并。但是左右兩部分的排序又重復上述過程,不斷地分裂成兩部分,直到最終都只有一個元素,此時所有的數組都是有序的。然后不斷合并兩個有序數組,直到最終成為一個整體。

其中的重點是合并步驟。假設數組為 AAAppp、qqqrrr 是數組下標?,F在子數組 A[p..q]A[p..q]A[p..q]
A[q+1..r]A[q+1..r]A[q+1..r] 都已排好序,我們要合并這兩個有序子數組以形成單一的有序數組并替代當前的 A[p..r]A[p..r]A[p..r]。

還是以撲克牌為例,假設現在有兩堆牌面朝上的牌,每堆都已從小到大排好序,最小的牌位于頂端。我們每一次都從兩堆牌的頂上選擇更小的那一張放入輸出堆中,這樣不斷循環直到一個牌堆為空,然后將剩余的牌堆全部放入輸出堆即可。合并過程用偽代碼表示如下:

MERGE(A, p, q, r) n1 = q-p+1 n2 = r-q let L[1..n1=1] and R[1..n2+1] be new arrays for i = 1 to n1L[i] = A[p+i-1] for j = 1 to n2R[j] = A[q+j] L[n1+1] = inf R[n2+1] = inf i = 1 j = 1 for k = p to rif L[i] <= R[i]A[k] = L[i]i = i+1else A[k] = R[j]j = j+1

值得注意的是可以使用哨兵來避免判斷數組溢出。在第 9-10 行,我們將兩個臨時列表的最后一位元素設置為無窮大,當一個牌堆已經到底的時候,其哨兵牌將大于另一牌堆的所有牌(哨兵牌除外)。

之后我們就可以把上述的合并過程當作歸并排序的一個子程序來使用。我們使用 MERGE?SORT(A,p,r)\mathrm{MERGE-SORT}(A, p, r)MERGE?SORT(A,p,r) 來對子數組 A[p..r]A[p..r]A[p..r] 排序:

MERGE_SORT(A, p, r) if p < rq = floor((p+r)/2) // 向下取整MERGE_SORT(A, p, q)MERGE_SORT(A, q+1, r)MERGE(A, p, q, r)

第 4-5 行對原序列不斷分解,當全部子序列都只包含一個元素時,就已經完成了對最小子序列的排序工作,然后就開始合并。

圖示如下:
分解:

合并:


實現:

以 Java 為例,C++代碼見文末。

首先實現并的部分,當我們有兩個有序數組需要合并時(這里采取的策略是用一個額外的數組 temptemptemp 來保存排好序的數組,然后將這個 temptemptemp 內的值返回到原數組),我們需要三個指針 left,midleft, midleft,midrightrightright , 分別比較兩個數組中較小的那個,保存到 temptemptemp 中。排序完畢后,將 temptemptemp 中的數據返回到原數組中。

這里用最后一次的排序舉例:

// java/*** 合并數組* * @param arr 排序的原始數組* @param left 左邊有序序列的初始索引* @param mid 中間索引* @param right 右邊索引* @param temp 臨時數組*/public static void merge(int[] arr, int left, int mid, int right, int[] temp) {int i = left; // 初始化 i ,左邊有序序列的初始索引int j = mid + 1; // 初始化 j ,右邊有序序列的初始索引int t = 0; // 指向temp數組的第一位// 將左右兩邊的數據填充到temp數組while (i <= mid && j <= right) {if (arr[i] <= arr[j]) {temp[t] = arr[i];i++;}else {temp[t] = arr[j];j++;}t++;}// 當一邊填充完畢時,另一邊數組一般還有剩余// 將剩余元素全部填充到temp數組// 如果左邊還有剩余while (i<=mid) {temp[t] = arr[i];i++;t++;}// 如果右邊還有剩余while (j <= right) {temp[t] = arr[j];j++;t++;}//將temp數組元素拷貝到arr數組,并不是將temp全部拷貝到arr,而是截取部分t = 0;int tl = left;while (tl <= right) {arr[tl] = temp[t];tl++;t++;}}

然后是拆分部分,主要是遞歸

代碼如下:

// javapublic static void mergeSort(int[] arr, int left, int right, int[] temp) {if (left < right) {int mid = (left + right)/2;// 向左分解mergeSort(arr, left, mid, temp);// 向右分解mergeSort(arr, mid+1, right, temp);// 合并merge(arr, left, mid, right, temp);}}

下圖是邏輯順序:


時間復雜度:

歸并排序的時間復雜度主要考慮兩個函數需要的時間,即 mergeSort 和 merge

merge 合并的時間復雜度為 O(n)O(n)O(n),因為全部都是長度為 nnn 的一次循環

利用之前說過的遞歸的時間復雜度公式,歸并排序的時間復雜度為 T(n)=2(T(n/2))+O(n)T(n) = 2(T(n/2))+O(n)T(n)=2(T(n/2))+O(n)

這個遞歸式可以用遞歸樹來解(假設解決最后一步子問題的時間是常數 ccc,則 nnn 個子問題花費的時間為 cncncn):

我們可以看到遞歸的每一層需要的時間都是 cncncn,總共有 logn+1logn+1logn+1 層,總的時間為 cn(logn+1)cn(logn+1)cn(logn+1) ,時間復雜度是 O(nlogn)O(nlogn)O(nlogn)。

空間復雜度:

歸并排序在每一次合并時需要臨時數組來儲存排好序的序列,最后一次排序,要儲存的數字最多,為 nnn,所以空間復雜度為 O(n)O(n)O(n)。

穩定性:

歸并排序可以保證等值元素之間的順序,因此是穩定的


相關章節
第一節 簡述
第二節 稀疏數組 Sparse Array
第三節 隊列 Queue
第四節 單鏈表 Single Linked List
第五節 雙向鏈表 Double Linked List
第六節 單向環形鏈表 Circular Linked List
第七節 棧 Stack
第八節 遞歸 Recursion
第九節 時間復雜度 Time Complexity
第十節 排序算法 Sort Algorithm
第十一節 冒泡排序 Bubble Sort
第十二節 選擇排序 Select Sort
第十三節 插入排序 Insertion Sort
第十四節 冒泡排序,選擇排序和插入排序的總結
第十五節 希爾排序 Shell’s Sort
第十六節 快速排序 Quick Sort
第十七節 歸并排序 Merge Sort


C++ 實現:

#include <iostream> #include <vector> using namespace std;/*** @brief Merge two ordered lists(nums[low..mid], nums[mid+1..high]).* * @param nums * @param low * @param mid * @param high */ void merge(vector<int>& nums, int low, int mid, int high) {int len1 = mid-low+1;int len2 = high-mid;vector<int> left(len1+1);vector<int> right(len2+1);for (int i=0;i<len1;i++) {left[i] = nums[i+low];}for (int j=0;j<len2;j++) {right[j] = nums[j+mid+1];}// 設置哨兵left[len1] = INT_MAX;right[len2] = INT_MAX;// 合并 int i = 0;int j = 0;for (int k=low;k<=high;k++) {if (left[i] <= right[j]) {nums[k] = left[i++];} else {nums[k] = right[j++];}}}void mergeSort(vector<int>& nums, int low, int high) {if (low < high) {int mid = (high-low)/2+low;mergeSort(nums, low, mid);mergeSort(nums, mid+1, high);merge(nums, low, mid, high);} }// test int main(int argc, char const *argv[]) {vector<int> nums = {4,7,2,9};mergeSort(nums, 0, nums.size()-1);for (auto num : nums) {cout << num << endl;}return 0; }

總結

以上是生活随笔為你收集整理的算法与数据结构(归并排序)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。