冒泡排序
基本思想:爲什麼叫冒泡排序呢,,是因爲每一次排序就像是魚兒吐泡泡,一個個泡泡從水底到水面會變得越來越大,泡泡就像元素,隨着算法的進行,較大的元素就會到後面去。如果升序排列,那麼就會對相鄰兩個元素進行比較,如果前者大於後者,那麼就交換這了兩個元素,直到最大的元素被排到最後一個位置,第一趟冒泡完成,進行第二趟冒泡,把次大的元素排到倒數第二的位置,重複這個過程。直到所有的元素都被冒泡到合適的位置,停止。
具體步驟:
代碼實現:
template<class T>//仿函數,本質是函數對象,對()進行重載,功能類似於函數
struct Less
{
bool operator ()(const T& left,const T&right)
{
return left < right;
}
};
template<class T>
struct Greater
{
bool operator ()(const T& left, const T&right)
{
return left > right;
}
};
template < class T,class compare=Less<T>>//使用模板參數,處理不同類型數據,以及指定排序方式,這裏默認降序
void bubblesort(T* array,int sz)
{
bool IsChanged = false;//
for (int i = 0; i < sz - 1; i++)
{
bool IsChanged = false;
for (int j = 0; j < sz - 1 - i; j++)
{
if (compare()(array[j], array[j + 1]))//仿函數
swap(array[j], array[j + 1]);
IsChanged = true;
}
if (IsChanged == false)//如果一次都沒交換,結束循環,提高程序效率
break;
}
}
算法性能
時間複雜度:平均時間複雜度O(N^2)最好和最壞情況下。
空間複雜度:O(1)
穩定性:穩定
快速排序
基本思想:首先,在待排序列中選擇一個key值,然後將其他元素以此和key值比較,比key值小的放到key的前面,比key大的放到key的後面,這樣就吧序列分成兩個區間,然後遞歸起左右區間,直到區間只剩一個元素的時候,那麼停止遞歸,此時可以認爲子區間是已經有序,然後一層一層的往上返回,結束後,整個區間就已經排好序。具體看下圖。
具體步驟:
- 首先,在待排序的區間選出來一個基準值key.
- 用key依次跟去他元素比較,把比key小的元素放到key的左邊,比key大的放到key的右邊,此時key就像是一個分水嶺,將數據分成兩部分。
- 遞歸排序key的左右區間,重複上述1,2步驟。
- 直到區間元素只剩一下的時候,停止遞歸。
實現方法
我目前瞭解的實現快排的方法有三種,分別是左右指針法,挖坑法,和前後指針法。
1.左右指針法
實現步驟:
- 定義兩個指針left和right分別指向待排序區間的第一個元素和最後一個元素,這裏我們選擇left指向的元素爲key,那麼左指針就需要先走。
- 左指針向右走,當尋找到比key大的元素就停下,右指針向左走,找到比key小的元素就停下來。
- 如果兩個指針沒有相遇,就交換左右指針指向的元素。
- 直到兩個指針相遇就結束,然後把左指針指向的位置賦值爲key。
思想流程圖:
實現代碼
int PartSort1(int *array, int left,int right)//區間左畢右畢
{
int key = right;
while (left < right)
{
//左指針尋找比key大的元素
while (left < right&&array[left] < key)
{
left++;
}
while(left<right&&array[right]>key)//右指針尋找比key小的元素
{
right--;
}
if (array[left] != array[right])
{
swap(array[left], array[right]);
}
}
array[left] = key;
return left;
}
2.挖坑法
具體步驟:
- 首先用left和right兩個指針標記待排序列第一個和最後一個元素,這裏選擇key爲right指向的元素,並且第一個坑設置在key的位置.
- 讓left向右尋找比key大的元素,找到了就停下來,把這個元素放到坑的位置,把left指向的位置設爲新的坑。
- 接下來right向左走,找比key小的元素,找到了就把這個元素填入坑中,並把right現在的位置設爲新的坑。
- 重複上述2,3步驟,直到left和right相遇停止,然後最後把坑的位置設置成key.
思想流程圖:
實現代碼:
int PartSort2(int *array, int left, int right)//挖坑法
{
int key = right;
int keng = right;
while (left < right)
{
while (left < right&&array[left] < key)
{
left++;
}
array[keng] = array[left];
keng = left;
while (left<right&&array[right]>key)
{
right--;
}
array[keng] = array[right];
keng = right;
}
array[keng] = key;
return keng;
}
3.前後指針法
具體步驟:
- 先讓pcur指向第一個元素,prev指向pcur的前一個元素,此時選取最後一個元素爲key。
- pcur向後走找到比key小的元素就停止,++prev,當prev和pcur不相等的時候,交換兩個位置的元素,相等不交換。
- pcur一直往後走,重複2過程,當走到右邊界的時候,++prev,並且交換prev和pcur位置的元素。
思想流程圖:
代碼實現:
int PartSort(int *array, int left, int right)
{
assert(array);
int key = right;
int pcur = left;
int prev = left - 1;
while (pcur<right)
{
if (array[++prev] !=array[ pcur]&&array[pcur]<key)
{
swap(array[prev], array[pcur]);
}
}
++prev;
swap(array[prev], array[pcur]);
}
快排的性能
時間複雜度:快排是一種已知道的最快的排序算法,它的平均時間複雜度爲O(NlogN),但是它也是一種不穩定的算法,當它的基準值選取的不合理的時候,那麼就會造成O(N^2)的時間複雜度
空間複雜度:O(logN)
穩定性:不穩定
所以說,選擇合理的基準值key,影響着快排的效率,上述的基準值都是取的是最後一個元素,下面我們將算法進行改進,通過幾種合理的取數法,使得快排左右區間儘可能的分配均勻。
1.三數取中法
基本思想:我們爲了把區間儘可能的分配均勻,在取基準值的時候,選取數組最左邊,最中間,最右邊的元素三個元素中中間大小的元素作爲基準值。
int GetMidIndex(int *array, int left, int right)
{
int mid = left + ((right - left) >> 1);
if (array[left] < array[right])
{
if (array[mid] < array[left])
return left;
else if (array[mid] > array[right])
return right;
else
return mid;
}
else
{
if (array[mid] > array[left])
return left;
else if (array[mid] < array[right])
return right;
else
return mid;
}
}
2.小區間優化
基本思想:當區間較小時候,一般認爲有13個元素以下,這個時候,快速排序就沒有直接插入的性能好了,因爲當區間比較小的時候,區間劃分的就比較多,快排就像一顆二叉樹一樣,每一次遞歸都相當於增加一層高度,當區間比較小的時候,就會快速增加二叉樹高度,降低了效率,因此我們在劃分到小區間的時候就改爲插入排序。
代碼實現:
void InsertSort(int *array,int size)
{
for (int i=1;i<size;i++)
{
int end = array[i];
int j = i - 1;
while (j >= 0 && array[j] > end)
{
array[j + 1] = array[j];
j--;
}
array[j + 1] = end;
}
3.非遞歸實現
基本思想:我們前面講的快排都是基於遞歸子區間來實現的,而當數據元素比較多的時候,難免遞歸的次數會非常多,而每次函數的遞歸都是一個函數的棧幀過程,十分消耗時間,因此可以藉助棧這種方式,來實現遞歸轉非遞歸。
代碼實現:
void QuickSortNoeR(int*array, int left, int right)
{
stack<int> s;
s.push(left);
s.push(right);
while (!s.empty())
{
int start = s.top();
s.pop();
int finish = s.top();
s.pop();
int div = PartSort1(array, start, finish);
if (start < div - 1)
{
s.push(div - 1);
s.push(start);
}
if (finish > div + 1)
{
s.push(finish);
s.push(div + 1);
}
}
}
這就是對於交換排序,本人的學習之談。