【資料結構】八大排序之快速排序(遞迴和非遞迴方法)
上一博文我們講了氣泡排序,但是由於他的時間複雜度過高為O(n*n),於是在氣泡排序的基礎上今天要說的是快速排序。
本文講述兩個內容:
1.快速排序的三種方法。
2.快速排序的優化
一.什麼是快速排序???
通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列。
中心思想就是:找一個基準值,把資料分為兩部分,然後去排序,類似於二分查詢。
1.快速排序的的時間複雜度:
平均為:O(n×log(n))
最壞情況下為:O(n * n)
2.快速排序不是穩定的排序
二.快速排序遞迴的三種方法:
1.挖坑法
2.交換指標法(左右指標)
3.交換指標(前後指標)
(1.)遞迴:
1.)挖坑法(遞迴)
1.在陣列中選取一個基準值,我在這裡選取的是陣列第一個資料
2.定義兩個指標(left,right)分別指向陣列的首位和末尾(left = 0,right = sz - 1)
3.將left看為第一個坑,然後用基準值和right指標所指的數比較如果比基準值大,則right指標向左移(減減)。反之則將right指標所指的值付給left所指的的值,letf指標向右移(加加)。
再將right指標所指位置看為一個坑,用left指標所指的值和基準值比較,如果比基準值小,則left指標向右移(加加)。反之則將left所指的值付給right指標所指的值,right指標向左移。
最後將point值賦給最後的坑的位置
我們以陣列int array[ ] = { 4, 7, 6, 5, 3, 2, 8, 1 }; 為例
程式碼展示:
void quickSort(int* array, int left, int right) { if (left >= right) return; int Index = partsort(array, left, right); //分開而治 //左半部分 quickSort(array, left, Index - 1); //右半部分 quickSort(array, Index + 1, right); } int partsort(int* array, int left, int right) { //基準值 int ponit = array[left]; //坑的位置 int temp = left; while (left <= right) { while (left <= right) { if (array[right] < ponit) { array[left] = array[right]; ++left; temp = right; break; } else --right; } while (left <= right) { if (array[left] > ponit) { array[right] = array[left]; --right; temp = left; break; } else ++left; } } array[temp] = ponit; return temp; }
2.交換指標法(左右指標)
1.和挖坑法一樣定義兩個指標(left和right)分別指向陣列的首部和陣列的尾部(left = 0,right = sz - 1)
2.還是以陣列第一個數為基準值 int ponit = array[left]
3.首先移動right指標,如果right指標所指的值比基準值小,則right指標不動,去移動left指標。否則right指標向左移動,知道移動到比基準值大的地方停下。
4.移動left指標,如果left指標所指的值比基準值大,則left不動,然後交換左右指標所指的值,否則left指標想右移動,知道找到比基準值大的值停下,然後交換兩個指標所指的值。
5.最後交換將ponit的值和left指標所指的值交換
我們以陣列int array[ ] = { 4, 7, 6, 5, 3, 2, 8, 1 }; 為例
程式碼展示:
//指標交換法(前後指標)--->遞迴
void quickSort(int* array, int left, int right)
{
if (left >= right)
return;
int Index = partsort(array, left, right);
//分開而治
//左半部分
quickSort(array, left, Index - 1);
//右半部分
quickSort(array, Index + 1, right);
}
int partsort(int* array, int left,int right)
{
int Index = left;
int ponit = array[Index];
while (left < right)
{
while (left < right)
{
if (array[right] < ponit)
break;
else
--right;
}
while (left < right)
{
if (array[left] > ponit)
break;
else
++left;
}
int temp = array[right];
array[right] = array[left];
array[left] = temp;
}
int num = array[left];
array[left] = array[Index];
array[Index] = num;
return left;
}
(2.)非遞迴法
在上邊的三種方法中我們都用了遞迴的方法,當我們要用非遞迴時,要立刻想到棧,因為遞迴的原理符合棧的先進後出的規則,遞迴和棧在面試題和演算法中總是聯絡在一起的。
程式碼展示:
int partsort(int* array, int left, int right)
{
//基準值
int ponit = array[left];
//坑的位置
int temp = left;
while (left <= right)
{
while (left <= right)
{
if (array[right] < ponit)
{
array[left] = array[right];
++left;
temp = right;
break;
}
else
--right;
}
while (left <= right)
{
if (array[left] > ponit)
{
array[right] = array[left];
--right;
temp = left;
break;
}
else
++left;
}
}
array[temp] = ponit;
return temp;
}
void QuickSort(int* array, int left, int right)
{
stack<int> s;
s.push(left);
s.push(right);
while (!s.empty())
{
int right = s.top();
s.pop();
int left = s.top();
s.pop();
//劃分左右部分的邊界線
int Index = partsort(array, left, right);
//左半部分
if (Index - 1 > left)
{
s.push(left);
s.push(Index - 1);
}
//右半部分
if (Index + 1 < right)
{
s.push(Index + 1);
s.push(right);
}
}
}
三.快速排序的優化
對於快速排序,根據我們上邊的程式碼我們可以看出最主要的是選取基準值,我上邊的程式碼都是以左邊的數為基準值。這樣就會產生一種情況:如果資料是正序或者逆序時,我們這樣選取的基準值顯然是不對的,這樣的其他資料都在基準值的一側,我們是無法劃分出兩個區域來分開排序的。為了解決這個問題就要用到以下兩個方法:
1.三數取中法
每次在排序之前,我們都無法知道資料是逆序或者是正序,如果每次去檢測那就太麻煩,所以我們選取三個數值(left,right,mid)如果是正序或者逆序那麼就直接選取中間的數值,反之任意選取兩邊任意一個數當作基準值。這樣就很容易將資料等分開來,大大提高了程式碼的效率。
(1.)挖坑法的三數取中法
int GetMid(int* array, int left, int right)
{
//獲取中間位置
int mid = left + ((right - left) >> 1);
//假設是正序(升序)
if (array[left] <= array[right])
{
//兩個if判斷到底是不是正序如果不是就返回邊上的,如果是就返回中間的
if (array[mid] < array[left])
return left;
else if (array[mid] > array[right])
return right;
else
return mid;
}
//假設是逆序
else
{
//兩個if語句判斷你是否為逆序,如果不是就返回邊上的,如果是就返回中間的
if (array[mid] > array[left])
return left;
else if (array[mid] < array[right])
return right;
else
return mid;
}
}
//挖坑法
void quickSort(int* array, int startIndex, int endIndex)
{
if (startIndex >= endIndex)
return;
//邊界
int Index = partsort(array, startIndex, endIndex);
//分開而治
//左半部分
quickSort(array, startIndex, Index - 1);
//右半部分
quickSort(array, Index + 1, endIndex);
}
int partsort(int* array, int left, int right)
{
int Index = left; //坑的位置
int num = GetMid(array, left, right);
int temp = array[num];
array[num] = array[left];
array[left] = temp;
int ponit = array[left]; //基準資料
while (left <= right)
{
while (left <= right)
{
if (array[right] < ponit)
{
array[left] = array[right];
++left;
Index = right;
break;
}
else
--right;
}
while (left <= right)
{
if (array[left] > ponit)
{
array[right] = array[left];
--right;
Index = left;
break;
}
else
++left;
}
}
array[Index] = ponit;
return Index;
}
(2.)指標替換法
int GetMid(int* array, int left, int right)
{
//獲取中間位置
int mid = left + ((right - left) >> 1);
//假設是正序(升序)
if (array[left] <= array[right])
{
//兩個if判斷到底是不是正序如果不是就返回邊上的,如果是就返回中間的
if (array[mid] < array[left])
return left;
else if (array[mid] > array[right])
return right;
else
return mid;
}
//假設是逆序
else
{
//兩個if語句判斷你是否為逆序,如果不是就返回邊上的,如果是就返回中間的
if (array[mid] > array[left])
return left;
else if (array[mid] < array[right])
return right;
else
return mid;
}
}
void quickSort(int* array, int startIndex, int endIndex)
{
if (startIndex >= endIndex)
return;
//邊界
int Index = partsort(array, startIndex, endIndex);
//分開而治
//左半部分
quickSort(array, startIndex, Index - 1);
//右半部分
quickSort(array, Index + 1, endIndex);
}
int partsort(int* array, int left,int right)
{
int Index = left;
int arr = GetMid(array, left, right);
int temp = array[arr];
array[arr] = array[left];
array[left] = temp;
int ponit = array[Index];
while (left < right)
{
while (left < right)
{
if (array[right] < ponit)
break;
else
--right;
}
while (left < right)
{
if (array[left] > ponit)
break;
else
++left;
}
int temp = array[right];
array[right] = array[left];
array[left] = temp;
}
int num = array[left];
array[left] = array[Index];
array[Index] = num;
return left;
}
2.直接插入法(這個程式碼在以後的排序中會展示)
由於是遞迴程式,每一次遞迴都要開闢棧幀,當遞迴到序列裡的值不是很多時,我們可以採用直接插入排序來完成,從而避免這些棧幀的消耗
以上就是我自己總結的快速排序,如果有什麼錯誤,歡迎大家指出,感激不盡!!!!或者有更好的方法,希望大家能評論告訴我,跪拜!!!!希望一起進步!!!