注意:以下所有的排序都是按照遞增的順序排序
參考鏈接:https://www.cnblogs.com/fnlingnzb-learner/p/9374732.html
1、選擇排序
選擇排序很簡單,就是先遍歷數組中的Size個元素,找到最小值放在第一位,然後在遍歷數組中後Size-1個元素,找到最小值放在第二位,一次類推。
時間複雜度:O(n*n),空間複雜度:O(1),不穩定(舉例說明:序列5,8,5,3,9,第一次會將第一個5和3互換位置,這樣兩個5的相對位置就改變了)
void SelectSort(vector<int>& nums) {
int Size = (int)nums.size();
for (int i = 0; i < Size - 1; ++i)
{
int Min = i;
for (int j = i + 1; j < Size; ++j)
Min = nums[Min] > nums[j] ? j : Min;
swap(nums[i], nums[Min]);
}
}
2、冒泡排序
冒泡排序就是每一次比較兩個相鄰的元素的大小,如果不是遞增順序,就調換位置。其實整個過程是和選擇排序相反的過程,每一次大循環,找到未排序元素中最大的元素,放置到最後。
時間複雜度:O(n*n),空間複雜度:O(1),穩定
void BubbleSort(vector<int>& nums) {
int Size = (int)nums.size();
for (int i = 0; i < Size; ++i)
//每循環一次,都可以找到前Size - i個元素中最大的元素,並放到nums[Size - i - 1]的位置上
{
for (int j = 0; j < Size - 1 - i; ++j)
if (nums[j] > nums[j + 1])
swap(nums[j], nums[j + 1]);
}
}
3、插入排序
可以理解爲前若干個元素是已經排好序的,將當前元素插入到前面若干個元素中,組成一個新的數組。
時間複雜度:O(n)~O(n*n),空間複雜度:O(1),穩定
時間複雜度隨着數組有序性越好,複雜度越低。
void InsertSort(vector<int>& nums) {
int Size = (int)nums.size();
for (int i = 1; i < Size; ++i)
//每次認爲前i個元素是排好序的子數組,然後將第i個元素插入到前i個元素中,也就是比他大的就交換元素位置
{
for (int j = i; j >= 1 && nums[j] < nums[j - 1]; --j)
swap(nums[j], nums[j - 1]);
}
}
4、希爾排序
希爾排序其實是優化版的插入排序,主要思想就是將輸入數組用gap(增量)劃分成若干個子數組,也就是0,gap,2×gap…是一組,1,gap+1,2×gap + 1…是一組,以此類推。然後將gap增加,最後子數組就合併成一個數組了。具體可以參考這篇文章,寫的通俗易懂:https://blog.csdn.net/qq_39207948/article/details/80006224
插入排序之所以可以進行優化,是因爲插入排序有這樣一個特性,就是時間複雜度在n~n×n之間,如果數組排序性越好,那麼時間複雜度越低。所以先將數組劃分成小數組,這樣即使是n×n複雜度,也不會很大,因爲數組個數少(也就是n小)。然後不斷合併,最後大數組的排序性比較好了,所以時間複雜度也就降低了。
時間複雜度:O(n)~O(n×n),空間複雜度:O(1),不穩定(舉例說明:序列7,5,5,8在第一次循環時,7和第二個5換位,就改變了兩個5的相對順序)
//這樣寫更容易理解,其實最快的只用一個函數來寫
void InsertSort(vector<int>& nums, int gap, int index)
{
for (int i = index; i - gap >= 0 && nums[i] < nums[i - gap]; i -= gap)
swap(nums[i], nums[i - gap]);
}
void ShellSort(vector<int>& nums) {
int Size = (int)nums.size();
for (int gap = Size / 2; gap >= 1; gap /= 2)
for (int i = gap; i < Size; ++i)
{
InsertSort(nums, gap, i);
}
}
5、歸併排序
歸併排序就是先分成小數組,再排序,所以需要記住的是主要函數Merge中是先分,再排序,這個遞歸需要記住。我自己想到在排序的步驟中可以用插入排序的方法,空間複雜度明顯降低,但是時間複雜度不穩定。
時間複雜度:O(nlog2n),空間複雜度:O(n),穩定
void Sort(vector<int>& nums, int start, int middle, int end)
{
//插入排序方法,空間複雜度爲1,但是時間複雜度不穩定
/*for (int i = middle + 1; i <= end; ++i)
for (int j = i - 1; j >= start && nums[j + 1] < nums[j]; --j)
swap(nums[j + 1], nums[j]);*/
//一般的歸併排序的方法,時間複雜度穩定
vector<int> v1;
vector<int> v2;
for (int i = start; i <= middle; ++i)
v1.push_back(nums[i]);
for (int i = middle + 1; i <= end; ++i)
v2.push_back(nums[i]);
v1.push_back(INT_MAX);
v2.push_back(INT_MAX);
int m = 0, n = 0;
for (int i = start; i <= end; ++i)
{
if (v1[m] > v2[n])
nums[i] = v2[n++];
else nums[i] = v1[m++];
}
}
void Merge(vector<int>& nums, int start, int end)
{
if (start < end)
{
int middle = start + (end - start) / 2;
Merge(nums, start, middle);
Merge(nums, middle + 1, end);
Sort(nums, start, middle, end);
}
}
void MergeSort(vector<int>& nums)
{
int Size = nums.size();
Merge(nums, 0, Size - 1);
}
6、快速排序
快速排序和歸併排序是反的,歸併排序是先分,然後再排序,快速排序是先按照中間那個數,將數組分成前後兩個數組,相當於先排序,再分。
時間複雜度:O(nlog2n)~O(n×n),空間複雜度:O(1)
int Sort(vector<int>& nums, int start, int end, int val)
{
int j = start;
for (int i = start + 1; i <= end; ++i)
if (nums[i] < val)
{
j++;
swap(nums[i], nums[j]);//這裏用了swap避免多餘的空間佔用
}
swap(nums[start], nums[j]);//這裏是將中間值nums[start]放到兩個子數組的中間。
return j;
}
void Merge(vector<int>& nums, int start, int end)
{
if (start < end)
{
int val = nums[start];//這裏做了簡化,每次用來劃分子數組的值選取start那個,爲了方便後面的劃分
int middle = Sort(nums, start, end, val);
Merge(nums, start, middle - 1);
Merge(nums, middle + 1, end);
}
}
void FastSort(vector<int>& nums)
{
int Size = nums.size();
Merge(nums, 0, Size - 1);
}
快排的變種:
①當快排分割到尺寸較小的子數組時,使用插入排序更快,在STL中的排序就是這樣優化的。
②三數取中,在快排中,時間複雜度主要取決於選取的切分值是否合適,這個值如果是較大或者較小值,那麼切分得到的兩個子數組會形成一個較大,一個較小的尺寸,不利於後續分割。所以如果切分值選取中位數是最好的,但是這樣比較困難,所以可以在數組中選取三個值,比如開頭,中間和末尾,比較選取這三個數中中間的那個值作爲切分值,更合理。
③三向切分,當數組中有大量重複元素時,可以使用這種優化,就是將一個數組分成三個,小於切分值,等於切分值,大於切分值這三種情況來看。
代碼如下:
pair<int, int> Sort(vector<int>& nums, int start, int end, int val)
{
//j可以理解爲0~j-1都是小於val的,k+1~end都是大於val的
int j = start, i = start + 1, k = end;
while (i <= k)
if (nums[i] < val)//小於切分值的放左邊
{
swap(nums[i++], nums[j++]);
}
else if (nums[i] > val)//大於切分值的放右邊
{
swap(nums[i], nums[k--]);
}
else i++;//等於的不動
return pair<int, int>(j, k);
}
void Merge(vector<int>& nums, int start, int end)
{
if (start < end)
{
int val = nums[start];//這裏做了簡化,每次用來劃分子數組的值選取start那個,爲了方便後面的劃分
pair<int, int> middle = Sort(nums, start, end, val);
Merge(nums, start, middle.first - 1);
Merge(nums, middle.second + 1, end);
}
}
void FastSort(vector<int>& nums)
{
int Size = nums.size();
Merge(nums, 0, Size - 1);
}
④可以用stl自帶的partition函數來寫快排,但是是差不多的。
7、堆排序
如果想要升序排列,就建立最大堆,然後將堆頂的元素放到數組最後,不斷減小堆的尺寸。
其實整個堆排序的過程是將原數組就看成一個二叉樹,然後不斷使用調整堆結構的函數,使得這個二叉樹具有最大堆的性質
時間複雜度:O(NlogN),空間複雜度:O(1),不穩定。
void AdjustHeap(vector<int>& nums, int index, int Size)//調整堆的結構,將下標爲index的元素放到堆中合理的位置
{
int lchild = index * 2 + 1;
while (lchild < Size)
{
//找到子節點中較大的那個
if (lchild + 1 < Size && nums[lchild] < nums[lchild + 1])
++lchild;
//將子節點和父節點比較,如果子節點較大,就和父節點互換位置
if (nums[lchild] <= nums[index]) break;
else {
swap(nums[lchild], nums[index]);
index = lchild;
lchild = index * 2 + 1;
}
}
}
void MakeHeap(vector<int>& nums, int Size)
{
//注意這裏的起始位置是(Size - 2) / 2,這裏代表最後一個元素(Size - 1)的父節點是
//((Size - 1) - 1) / 2,所以起始位置就是這個
for (int i = (Size - 2) / 2; i >= 0; --i)
AdjustHeap(nums, i, Size);
}
void HeapSort(vector<int>& nums)//堆排序就是每次構建一個最大堆,然後將最大堆的堆頂元素放到堆的最後一個元素,然後堆尺寸減1,繼續構建最大堆。
{
int Size = nums.size();
MakeHeap(nums, Size);
for (int i = Size - 1; i > 0; --i)
{
swap(nums[i], nums[0]);
MakeHeap(nums, i);
}
}
8、桶排序
桶排序就是用哈希表來排序,比如如下就是10以內不重複的同排序簡單寫法:
void TongSort(int *score,int num)
{
int a[11];
for(int i=0;i<=10;i++)
a[i]=0;
for(int i=0;i<num;i++)
{
int temp=score[i];
++a[temp];
}
for(int i=0;i<11;i++)
{
int num_print=a[i];
for(int j=1;j<=num_print;j++)
cout<<i<<" ";
}
}
9、計數排序
這種方法我覺得和桶排序差不多,我在桶排序中的例子也算是基數排序的例子,就是用一個足夠大的數組,將元素作爲數組的下標,但是桶排序可以有更復雜的表示方法,所以基數排序更簡單。
10、基數排序
就是隻用一個大小爲10的數組,從各位,十位,百位的順序開始遍歷.如圖所示:
代碼如下:
/*
*求數據的最大位數,決定循環的次數
*/
int maxbit(vector<int> data)
{
int Size = data.size();
int bit = 1, div = 10;
for (int i = 0; i < Size - 1; ++i)
{
while (data[i] > div)
{
bit++;
div *= 10;
}
}
return div;
}
void radixsort(vector<int> &arr){
const int buckets_number = 10;
vector<vector<int> > buckets(10);//10個桶,每個桶是vector<int>
int len = maxbit(arr);
int r = 1;
for (int i = 0; i < len; i++){
for (int x : arr){
int tmp = x / r;
int index = tmp%buckets_number;
buckets[index].push_back(x);
}
int j = 0;
for (int k = 0; k < 10; ++k){
for (int x : buckets[k]){
arr[j] = x;
j++;
}
buckets[k].clear();
}
r = r * 10;
}
}
11、總結