八大排序演算法
直接插入排序(穩定)
我們認為在一組數中,可以分為三個部分,有序區,無序區和待排序區,例如下圖:
藍色部分是有序區,黃色部分表示即將要排序的,灰色是無序區,那麼我們怎麼在有序部分找到黃色應該插入的位置呢? 1.從後往前找。可以從藍色部分的後面往前找,分別和黃色比較,找到相應的位置 2.二分方式找。 程式碼如下:
void InsertSort(int array[], int size)
{
int key;
int i, j;
for (i = 1; i < size; i++)
{
key = array[i];
for (j = i - 1; j >= 0; j--)
{
if (key >= array[j])
{
break;
}
else {
array[j + 1] = array[j];
}
}
array[j+1] = key;
}
}
時間複雜度: 最好/最壞 O(n)/O(n^2) 空間複雜度: O(1)
希爾排序(不穩定)
希爾排序也是插排的一種,在排序之前會先做預排序,例如下面這組資料
希爾排序會先分組進行插敘,例如上面的陣列相同顏色的圈代表一組,先在每一組進行排序,然後不斷的重複,那麼這就需要考慮分組策略了: 1.分組間隔越大,排序次數越少 2.分組間隔越小,結果越有序 一般情況下:我們會把一組資料分為幾組呢?一般情況gap=size/3+1;
程式碼:
void _InsertSort(int array[], int size, int gap)
{
for (int g = 0; g < gap; g++)
{
int key;
int i, j;
for (i = gap + g; i < size; i+=gap) {
key = array[i];
for (j = i - gap; j >= 0; j -= gap) {
if (key >= array[j]) {
break;
}
else {
array[j + gap] = array[j];
}
}
array[j + gap] = key;
}
}
}
void ShellSort(int array[], int size)
{
int gap = size;
while (1) {
gap = gap / 3 + 1;
_InsertSort(array, size, gap);
if (gap == 1)
{
break;
}
}
}
時間複雜度: 最好/平均/最差 O(n)/O(n^(1.2~1.3) ) /O(n^2) 空間複雜度: O(1)
選擇排序(不穩定)
每次在序列中選出最大(最小),交換到最後,簡而置之,然後迴圈
程式碼:
void Swap(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
void SelectSort(int array[], int size)
{
for (int i = size; i >1; i--)
{
int max = 0;
for (int j = 1; j < i; j++)
{
if (array[j] > array[max]) {
max = j;
}
}
Swap(array + max, array + i - 1);
}
}
上面這種是每次選出最大的或者最小放好位置,當然,我們也可以,每次既選出最大的,也選出最小的同時放好位置。
程式碼:
void Swap(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
void SelectSort1(int array[], int size)
{
int left = 0;
int right = size - 1;
while (left < right)
{
int min = left;
int max = left;
for (int j = left + 1; j <= right; j++)
{
if (array[j] > array[max])
{
max = j;
}
if (array[j] < array[min])
{
min = j;
}
}
Swap(array + left, array + min);
if (max == left)
{
max = min;
}
Swap(array + right, array + max);
left++;
right--;
}
}
時間複雜度:O(n^2) 空間複雜度:O(1)
堆排序(不穩定)
堆排序的思想其實就是,每次找到最大值與最後一個元素交換,然後向下調整。排序的過程其實很簡單,主要在於建立大堆和堆的向下調整。
void AdjustDown(int array[], int size, int root)
{
int left = 2 * root + 1;
int right = 2 * root + 2;
if (left >= size)
{
return;
}
int max = left;
if (right<size&&array[right]>array[left])
{
max = right;
}
if (array[root] >= array[max])
{
return;
}
Swap(array + root, array + max);
AdjustDown(array, size, max);
}
void CreateHeap(int array[], int size)
{ //建立大堆
//從最後一個非葉子結點到0
//不斷的進行向下調整
for (int i = size / 2 - 1; i >= 0; i--)
{
AdjustDown(array, size, i);
}
}
void HeapSort(int array[], int size)
{
CreateHeap(array, size);
for (int i = 0; i < size; i++)
{
Swap(array, array + (size - i - 1));
AdjustDown(array,size-i-1,0);
}
}
時間複雜度:O(N*logN) 空間複雜度:O(1)
氣泡排序(穩定)
氣泡排序是最常見的排序,思想也很簡單,就是挨個交換,不斷迴圈。
void BubbleSort(int array[], int size)
{
for (int i = 0; i < size - 1; i++)
{
int flag = 1;
for (int j = 0; j < size - 1 - i; j++)
{
if (array[j] > array[j + 1])
{
Swap(array + j, array + j + 1);
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
時間複雜度: 最好/平均/最壞 O(n) O(n^2) O(n^2) 空間複雜度: O(1)
快速排序(不穩定)
基本思想:分治演算法 1.找一個基準值 1>找邊上的 2.比基準值小的都放到基準值的左邊 比基準值大的都放到基準值的右邊 3.終止條件 1>小區間有序 區間長度==1 2>小區間沒有數 區間長度<=0
將區間按照基準值劃分為左右兩半部分的常見方式有:
方法一:hoare法
快速排序是Hoare於1962年提出的一種二叉樹結構的交換排序方法,其基 本思想為:任取待排序元素序列中的某元素作為基準值,按照該排序碼將 待排序集合分割成兩子序列,左子序列中所有元素均小於基準值,右子序 列中所有元素均大於基準值,然後最左右子序列重複該過程,直到所有元 素都排列在相應位置上為止。
int Partition(int array[], int left, int right)
{
int begin = left;
int end = right;
while (begin < end)
{
while (begin < end&&array[begin] <= array[right])
{
begin++;
}
while (begin < end&&array[end] >= array[right])
{
end--;
}
Swap(array + begin, array + end);
}
Swap(array + begin, array + right);
return begin;
}
void _QuickSort(int array[],int left,int right)
{
if (left == right)
{
return;
}
if (left > right)
{
return;
}
int div = Partition(array, left, right);
_QuickSort(array, left, div - 1);
_QuickSort(array, div + 1, right);
}
void QuickSort(int array[], int size)
{
_QuickSort(array, 0, size - 1);
}
挖坑法
挖坑法其實就是提前將基準值存起來,這樣基準值的位置其實就是一個坑,可以被別的元素填。挖坑法的具體過程就是將最後一個元素作為基準值,然後begin從前遍歷,遇到比基準值大的就去填基準值的坑,end從後往前遍歷,遇到比基準值小的再去填begin的坑。
int Partition_02(int array[], int left, int right)
{
int begin = left;
int end = right;
int priot = array[right];
while (begin < end)
{
while (begin < end&&array[begin] <= priot)
{
begin++;
}
array[end] = array[begin];
while (begin < end&&array[end] >= priot)
{
end--;
}
array[begin] = array[end];
}
array[begin] = priot;
return begin;
}
void _QuickSort(int array[],int left,int right)
{
if (left == right)
{
return;
}
if (left > right)
{
return;
}
int div = Partition_01(array, left, right);
_QuickSort(array, left, div - 1);
_QuickSort(array, div + 1, right);
}
void QuickSort(int array[], int size)
{
_QuickSort(array, 0, size - 1);
}
快慢指標法
快慢指標也叫前後指標,當然並不是一個從前一個從後遍歷,而是一前一後遍歷,兩個指標都從頭開始向後遍歷,快的指標每次都向後移動,只有當快的指標所指的值小於基準值時,快慢指標所指的值進行交換,並且慢指標向後移動,否則,慢指標不動。
int Partition_03(int array[], int left, int right)
{
int fast = left;
int slow = left;
while (fast<right)
{
if (array[fast] < array[right])
{
Swap(array + fast, array + slow);
slow++;
fast++;
}
else {
fast++;
}
}
Swap(array + right, array + slow);
return slow;
}
void _QuickSort(int array[],int left,int right)
{
if (left == right)
{
return;
}
if (left > right)
{
return;
}
int div = Partition_03(array, left, right);
_QuickSort(array, left, div - 1);
_QuickSort(array, div + 1, right);
}
void QuickSort(int array[], int size)
{
_QuickSort(array, 0, size - 1);
}
時間複雜度:O(nlogn) 空間複雜度:O(1)