主頁 > 後端開發 > 兩萬字搞定《資料結構》 八大排序 必讀(建議收藏)

兩萬字搞定《資料結構》 八大排序 必讀(建議收藏)

2021-10-19 09:10:26 後端開發

前言:本章將介紹常見八大排序包括如下直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快排、歸并排序以及計數排序(基數排序和桶排序面試基本不涉及,本文忽略了,有興趣的讀者可以自行補充),本章內容是重點中的重點!!!鐵子們務必全部掌握!!!

image-20210930205216242


文章目錄

  • 1.插入排序
    • 1.1直接插入排序
    • 1.2希爾排序
  • 2.選擇排序
    • 2.1 選擇排序(二元改進版)
    • 2.2 堆排序
  • 3.交換排序
    • 3.1 冒泡排序
    • 3.2 快速排序
      • 3.2.1 Hoare
      • 3.2.2 前后指標法
      • 3.2.3 挖坑法
    • 3.3 快速排序(非遞回)
  • 4.歸并排序
    • 4.1 遞回實作歸并排序
    • 4.2 迭代實作歸并排序
  • 5.計數排序
  • 6.八大排序對比
    • 6.1八大排序的性能測驗評估
    • 6.2 各個排序的穩定性如何判斷?
    • 6.3八大排序時間/空間復雜度一覽


1.插入排序

在這里插入圖片描述

1.1直接插入排序

基本思想

把一個數插入到有序區間,保持這個區間有序,當前第n+1個數插入到前面,前面的arr[0]到arr[n-1]已經排好序,此時用arr[n]與前面的arr[n-1], arr[n-2]…的值,進行比較找到合適的位置將arr[n]進行插入,原來位置上的元素順序后移實作了插入

代碼實作

//插入排序
void InsertSort(int* a, int n){
    //控制起始條件
    //注意控制好終止條件,這里的end的位置是在倒數第二個位置,所以要-1
    for(int i=0; i<n-1;i++){ 
        //單趟插入
        int end = i;
        int temp = a[end + 1]; //有序區間的后面
        while(end>=0){ //end到-1就終止了
            if(a[end]>temp){
                a[end+1] = a[end];
                --end;
            }else{
                break;
            }
        }
        //兩種情況:第一種在最右邊,第二種在最左邊,end為-1了,始終放在end后面
        a[end+1] = temp;    
    }
}

時間復雜度

插入排序的時間復雜度也是O(N^2),在接近有序的情況下他的時間復雜度是O(N),因為遍歷一遍就可以出結果了,空間復雜度O(1),


1.2希爾排序

希爾排序(Shell’s Sort)是插入排序的一種又稱“縮小增量排序”(Diminishing Increment Sort),是直接插入排序演算法的一種更高效的改進版本,
在這里插入圖片描述

基本思想

希爾排序就是在處理一些極端情況比較高效,比如在上面的插入排序時如果我們在原陣列降序的情況下去排升序,那么我們交換的次數是十分多的,也可以說是插入排序的最壞的情況,這個時候聰明的先輩想到了希爾排序,將陣列分成了gap組,然后可以理解為分別處理每一個小組,gap從5 – 2 – 1的程序在降序的情況下,排在后面的數值小的數能步子更大排到前面,當gap為1的時候實際上就是進行了一次插入排序,設定gap的程序我們也稱之為預排序,

gap越小,越接近有序,gap越大,越不接近有序;
但是gap越小挪動越慢,gap越大挪動越快;

代碼實作

void ShellSort1(int* a, int n)
{
    int gap = n;

    while (gap>1)//別傻乎乎的加等號啊,死回圈
    {
        gap = gap / 3 + 1;end的范圍是[0,n-gap)
        
        for (int i = 0; i < n - gap; i++)//并排走
        {
            int end = i;
            int temp = a[end + gap];
            while (end>=0)
            {
                //當前的end的值比tmp大就要往end+gap位置挪
				//所以要提前保存end+gap的值
                if (temp < a[end])
                {
                    a[end + gap] = a[end];
                    end = end-gap;
                }
                else
                {
                    break;
                }
            }
            a[end + gap] = temp;
        }
    }
}

時間復雜度

O(N^1.3),一般gap建議以gap/3+1的步驟走,


2.選擇排序

簡單選擇排序思路如下動圖所示,就是只針對頭部的一個數進行對比,代碼實作大家可以自己敲敲!

在這里插入圖片描述

2.1 選擇排序(二元改進版)

基本思想

優化的選擇排序,每次可以選擇一個最大的和一個最小的,然后把他們放在合適的位置,即最小的放在第一個位置,最大的放在最后一個位置,
然后再去選擇次小的和次大的,依次這樣進行,直到區間只剩一個值或沒有,

代碼實作

void SelectSort(int* a, int n)
{
	assert(a);
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		int min = begin, max = begin;
		for (int i = begin; i <= end; i++)//注意起點是begin
		{
			if (a[i] >= a[max])
				max = i;
 
 
			if (a[i] < a[min])
				min = i;
		}
		//最小的放在
		Swap(&a[begin], &a[min]);
		//如果最大的位置在begin位置
		//說明min是和最大的交換位置
		//這個時候max的位置就發生了變換
		//max變到了min的位置
		//所以要更新max的位置
		if (begin == max)
			max = min;
 
		Swap(&a[end], &a[max]);
		++begin;
		--end;
	}
}

時間復雜度

O(N^2),最壞的排序

2.2 堆排序

堆排之前文章詳細介紹過,具體細節歡迎點擊查閱
在這里插入圖片描述

基本思想

細節去查閱之前的文章,現在就強調一點:排升序要建大堆,排降序建小堆

這里以升序為例:先建堆,排升序建大堆,選出最大的數將其放到最后面,然后滿足大小堆后即可做向下調整動作,

代碼實作

//堆排序
void AdjustDown(int* a, int n, int parent){
    int child = parent*2 + 1;
    while(child < n){
        if(child+1<n && a[child+1] > a[child]){
            ++child;
        }
        if(a[child]>a[parent]){
            Swap(&a[child], &a[parent]);
            parent = child;
            child = parent*2+1;
        }else{
            break;
        }
    }
    
}
void HeapSort(int* a, int n){
    //排升序建大堆 O(N)
    for(int i=(n-1-1)/2; i>=0; i--){
        AdjustDown(a, n, i);
    }
    
    //O(N*logN)
    int end = n - 1;
    while(end > 0){
        Swap(&a[0], &a[end]);
        AdjustDown(a, end, 0); //是不是妙不可言hhh!
        end--;
    }
}

記錄下寫堆排時犯的錯誤(讀者可以跳過,這是留給作者復習自用的):

image-20211014164846732

image-20211014164934750

邊界問題,畫圖畫圖,冷靜分析!!!

時間復雜度

時間復雜度是O(N*logN),空間復雜度O(1)


3.交換排序

3.1 冒泡排序

在這里插入圖片描述

基本思想

以升序為例,每一趟的冒泡排序都是把一個最大的數放到最后面,如果 a[i-1]>a[i],我們將i-1,i的值進行交換,依次回圈反復,

代碼實作

void BubbleSort(int* a, int n){
    
    for(int j=0; j<n; j++){
        int flag = 0;
        for(int i=1; i<n-j; ++i){
            if(a[i] < a[i-1] ){
                Swap(&a[i], &a[i-1]);
                flag = 1;
            }
        }
        if(flag == 0){
            break;
        }
    }
}

時間復雜度

O(N^2)

3.2 快速排序

3.2.1 Hoare

在這里插入圖片描述

基本思想

選一個關鍵key,一般都是選擇頭,
單趟:key放在他正確的位置上,key的左邊值比key小,key右邊值比key大(這是key一趟下來排完后最終要放的位置)
單趟拍完,再想辦法讓左邊區間有序,key的右邊區間有序

那么還有優化解決方案:
第一種是取隨機值做下標
第二種是獲取這三個數中不是最大,也不是最小的那個值的下標,這種情況下不會有最壞情況,因為有三陣列取中

代碼實作

三數取中(為了優化)

//三數取中
int MidIndex(int* a, int left, int right)
{
	int mid = left + (right - left) / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else //a[left] > a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}
void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//不妨思考一下我們進行“三數取中”的意義是什么?

單趟排序:


//一個單趟進行的排序操作的時間復雜度是多少?思考下一次完整的快排需要進行多少趟這樣的單趟排序?

int PartSort1(int* a, int left, int right)
{
	int midi = MidIndex(a, left, right);
	Swap(&a[left], &a[midi]);

	//最左邊的做key為例
	int key = left;
	while (left<right)
	{
		//因為我們是最左邊的取key,所以必須是右邊先走找比key小的,思考下為什么?
		//右邊先走
		while (left < right && a[right] >= a[key])
		{
			--right;
		}
		//然后左邊走
		while (left < right && a[left] < a[key])
		{
			++left;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[key]);//此時left已經和right相遇,一樣的
	return left;
}

全趟排序(遞回):

void QuickSort(int* a, int left, int right)
{
	//當區間分割到只剩一個或者沒有的時候就回傳
	if (left >= right)
		return;
	//確定一個位置,劃磁區間遞回
	//分為[left,key-1]   key   [key+1,right]
	//int key = PartSort1(a, left, right);
	int key = PartSort2(a, left, right);
	QuickSort(a, left, key - 1);
	QuickSort(a, key + 1, right);
}

第一個問題:不妨思考一下我們進行“三數取中”的意義是什么?

如果我們不進行“三數取中”,快排如果遇見最壞的情況——有序,時間復雜度將會變成O(N^2),如果加了“三數取中”,這一最壞情況將會不復存在(后邊倆種單趟排序同理),當然了,實際面試程序當中時間不夠沒必要再來寫一個“三數取中”,面試爭分奪秒啦!

第二個問題:一個單趟進行的排序操作的時間復雜度是多少?思考下一次完整的快排需要進行多少趟這樣的單趟排序?

一個單趟的時間復雜度是O(N),一個完整的快排需要O(logN)趟這樣的單趟排序,

第三個問題:為什么key選擇最左邊的值,就要先讓右邊的數先走先去找小?

為了確保最后相遇時的a[left]<a[key],只要讓右邊的數先走,最后停下來時無論是“左邊遇到右邊”還是“右邊遇到左邊”,都滿足a[left]<a[key],

時間復雜度

一整個快排:O(N*logN)

3.2.2 前后指標法

基本思想

1.cur往前走,找到比key小的資料

2.找到比key小的資料以后,停下來,++prev

3.交換prev和cur指向位置的值

直到cur到達最右邊的位置結束!

cur還沒遇到比key大的資料之前,prev緊跟著cur,cur遇到比key大的值以后,prev和cur之間間隔著一段比key大的資料,

代碼實作

int PartSort2(int* a, int left, int right)
{
	int midi = MidIndex(a, left, right);
	Swap(&a[midi], &a[left]);

	//這里key選取最左邊的元素為例
	int key = left;
	int prev = left, cur = prev + 1;

	while (cur<=right)
	{
		if (a[cur] < a[key] && ++prev != cur)//防止自己與自己交換
		{
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
    //cur走到末尾啦,交換一下,
	Swap(&a[prev], &a[key]);//這里可以保證交換之前a[prev]一定小于a[key],思考下為啥?
	return prev;
}

答案:跳出while回圈的a[prev],在跳出回圈之前剛與a[cur]交換過,而a[prev]與a[cur]交換的條件就是a[cur]小于a[key],所以可以保證交換跳出while回圈后發生最后一次交換之前a[prev]一定小于a[key],

全趟排序(遞回):

void QuickSort(int* a, int left, int right)
{
	//當區間分割到只剩一個或者沒有的時候就回傳
	if (left >= right)
		return;
	//確定一個位置,劃磁區間遞回
	//分為[left,key-1]   key   [key+1,right]
	//int key = PartSort1(a, left, right);
	int key = PartSort2(a, left, right);
	QuickSort(a, left, key - 1);
	QuickSort(a, key + 1, right);
}

時間復雜度

一整個快排:O(N*logN)

3.2.3 挖坑法

在這里插入圖片描述

基本思想

挖坑法可以選擇在0索引處挖坑(即把數拿走保存),然后從右邊找一個小的填坑,再從左邊找一個大的埋住右邊的坑,以此反復回圈,直到left與right相遇,最后把key放入相遇點(最后一個坑位)即可,

代碼實作

int PartSort3(int* a, int left, int right)
{
	int midi = MidIndex(a, left, right);
	Swap(&a[midi], &a[left]);

	//這里key取最左邊的數,讓右邊的先開始走找小
	int hole = left;
	int key = a[left];

	while (left < right)
	{
		//先找右邊比key小的,填到左邊的坑里面去
		while (left < right && a[right] >= key)
		{
			right--;
		}
		a[hole] = a[right];
		hole = right;

		//再找左邊比key大的,找到就交換坑位
		while (left<right&&a[left]<key)
		{
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[left] = key;//最后把key放到相遇點
	return left;
}

全趟排序(遞回):

void QuickSort(int* a, int left, int right)
{
	//當區間分割到只剩一個或者沒有的時候就回傳
	if (left >= right)
		return;
	//確定一個位置,劃磁區間遞回
	//分為[left,key-1]   key   [key+1,right]
	//int key = PartSort1(a, left, right);
	int key = PartSort3(a, left, right);
	QuickSort(a, left, key - 1);
	QuickSort(a, key + 1, right);
}

時間復雜度

一整個快排:O(N*logN)

3.3 快速排序(非遞回)

我們前面實作快排是采用遞回的方式,但是遞回本身是有“原罪”的,這個“原罪”在于如下:

1.當遞回深度過大的時候,遞回程式本身可能沒用錯誤,但是編譯之后會報錯——堆疊溢位(stack overflow),

2.性能問題(某些書上提到的,但是現在編譯優化得很好,這個問題不大),

任何一個遞回程式,我們要把他改成非遞回程式有如下倆種方式:

1.回圈(但是有的東西是不好改成回圈的,比如二叉樹的遍歷、快排等)

2.“堆疊”模擬(這個“堆疊”是資料結構中的“堆疊”,不是系統內部那個“堆疊”,一般用到堆疊難度都是略大的)

這里的快排改非遞回用的就是“堆疊”模擬,

基本思想

非遞回的在這里借助堆疊,依次把我們需要單趟排的區間入堆疊,依次取堆疊里面的區間出來單趟排,再把需要處理的子區間入堆疊,以此回圈,直到堆疊為空的時候即處理完畢,

非遞回代碼實作

void QuickSortNonR(int* a, int left, int right)
{
	//非遞回,我們可以處理當前的區間,再處理磁區間
	//先入右,后入左,就先拿到左
	Stack s;
	StackInit(&s);
	StackPush(&s,right);
	StackPush(&s,left);
	while (!StackEmpty(&s))
	{
		left = StackTop(&s);
		StackPop(&s);
		right = StackTop(&s);
		StackPop(&s);
		//處理當前區間 [left,right]
		int key = PartSort3(a, left, right);

		//劃分左右區間,分別入堆疊
		//[left,key-1]    key    [key+1,right]
		//先入右區間,區間有兩個值才需要處理
		if (key + 1 < right)
		{
			StackPush(&s, right);
			StackPush(&s, key + 1);
		}
		//再入左區間
		if (left < key - 1)
		{
			StackPush(&s, key - 1);
			StackPush(&s, left);
		}
	}	
}

時間復雜度

最優的時間復雜度是O(nlogn),最差的空間O(n^2) ,因為進行了三數取中,不存在最差情況,


4.歸并排序

在這里插入圖片描述

4.1 遞回實作歸并排序

基本思想

我們可以把一個陣列分成兩半,對于每一個陣列當他們是有序的就可以進行一次合并操作,對于他們的兩個區間進行遞回,一直遞回下去劃磁區間,當區間只有一個值的時候我們就可以進行合并回傳上一層,讓上一層合并再回傳,

代碼實作

void _MergeSort(int* a, int left, int right,int* newArr)
{
	if (left >= right)
		return;
	int mid = left + (right - left) / 2;
	//[left,mid][mid+1,right]
	_MergeSort(a, left, mid,newArr);
	_MergeSort(a, mid + 1, right,newArr);
	//走到這里已經是左右區間有序
	//將兩個區間合并成一個區間
	//拷貝到newArr當中,排完再放回
	int index = left;
	int begin1 = left, end1 = mid;
	int begin2 = mid+1,end2 = right;

	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			newArr[index++] = a[begin1++];
		}
		else
		{
			newArr[index++] = a[begin2++];
		}
	}
	//走到這里一定有一邊沒有走完
	while (begin1 <= end1)
	{
		newArr[index++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		newArr[index++] = a[begin2++];
	}
	//拷貝回元素組  letf -- right 的位置
	for (int i = left; i <= right; ++i)
	{
		a[i] = newArr[i];
	}
}
void MergeSort(int* a, int n)
{
//歸并排序就是在左右區間有序重新組合起來
//所以保證左右區間都是有序,遍歷到葉子就可以
	int* newArr = (int*)malloc(sizeof(int) * n);
	int left = 0;
	int right = n - 1;
	_MergeSort(a, left, right,newArr);
}

時間復雜度

O(NlogN),可以看出他的遞回程序中每次都將一組平均分,分完后高度大概是logN,空間復雜度O(N)

4.2 迭代實作歸并排序

跟快排類似,遞回會帶給快排的問題同樣會給歸并排序帶來,所以嘗試用非遞回方式!

任何一個遞回程式,我們要把他改成非遞回程式有如下倆種方式:

1.回圈(但是有的東西是不好改成回圈的,比如二叉樹的遍歷、快排等)

2.“堆疊”模擬(這個“堆疊”是資料結構中的“堆疊”,不是系統內部那個“堆疊”,一般用到堆疊難度都是略大的)

這里歸并排序非遞回實作就是采用“回圈”,

基本思想

迭代實作可以用回圈來實作,這里我們根據遞回思想其實很容易知道,我們控制迭代從最小的子問題出發,保存最小子問題的值,然后提供給后面用,這其實就是一個動態規劃的思想,我們可以從利用子問題的解,解決 “大BOSS”

代碼實作

void MergeSortNonR(int* a, int n)
{
	//int a[] = { 8,4,5,7,1,3,6,2,7,8 };
	
	int* newArr=(int*)malloc(sizeof(int)*n) ;
	int groupNum = 1;
	int left;
	int right;
	//動態規劃的思想,當我們把最小的問題切割
	while(groupNum<n/2+1)
	{
		for (int i = 0; i < n; i += (2*groupNum))
		{
		//分成兩組[begin1,end1][begin2,end2]
		int begin1 = i;
		int end1 = i + groupNum - 1;
		int begin2 = i + groupNum;
		int end2 = i + 2 * groupNum - 1;
		//處理兩種情況,當end1已經越界,說明處理end1的邊界
		if (end1 >= n)
		{
			end1 = n - 1;
		}
		//當end1越界,理所當然的begin2和end2都越界了
		//這里可能的[begin1,end1]區間,也需要拷貝到臨時陣列,再拷回原陣列
		if (begin2 >= n)
		{
			//表示右區間不存在
			begin2 = n;
			end2 = n-1;
		}
		else if (begin2 < n && end2 >= n)
		{
			end2 = n - 1;
		}

		left = begin1;
		right = end2;
		//index用于放到臨時陣列newArr當中的
		int index = begin1;

			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					newArr[index++] = a[begin1++];
				}
				else
				{
					newArr[index++] = a[begin2++];
				}
			}
			//走到這里一定有一邊沒有走完
			while (begin1 <= end1)
			{
				newArr[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				newArr[index++] = a[begin2++];
			}
			//拷貝回元素組  letf -- right 的位置
			for (int x = left; x <= right; ++x)
			{
				a[x] = newArr[x];
			}
		}
		groupNum*=2;
	}
	free(newArr);
	newArr = NULL;
}

時間復雜度

O(NlogN)


5.計數排序

在這里插入圖片描述

基本思想

1.統計原陣列中每個值出現的次數

2.排序:遍歷Count陣列,對應位置的值出現多少次就往原陣列寫幾個這個值

當然,在對于資料比較大的時候我們可以通過相對映射,讓(該值-min)后的陣列加一,最后還原回去即可,

代碼實作

void CountSort(int* a, int n)
{
	//int a[] = { 31,24,25,16,1,0,79 };

	//遍歷一遍找到最大和最小,然后開大一的陣列
	int max = a[0];
	int min = a[0];
	for (int i = 1; i < n; ++i)
	{
		if (max < a[i])
		{
			max = a[i];
		}
		if (min > a[i])
		{
			min = a[i];
		}
	}
	int size = max - min + 1;
	int* tmp = (int*)calloc(size,sizeof(int));
	//將a遍歷映射到tmp當中,a的長度是n,tmp的長度只有size
	for (int i = 0; i < n; ++i)
	{
		tmp[a[i] - min]++;//tmp[i]存放的是這個值出現的次數
	}
	//在按照tmp當中的存放放回去
	int index = 0;
	for (int i = 0; i < size; ++i)
	{
		while (tmp[i] > 0)
		{
			//這里應該是下標+映射的
			a[index++] = i + min;
			--tmp[i];
		}
	}
}

時間復雜度

計數排序的時間復雜度是O(N),空間復雜度是O(max-min),就是我們開的陣列是這個區間的范圍差,


6.八大排序對比

6.1八大排序的性能測驗評估

// 測驗排序的性能對比
void TestOP()
{
srand(time(0));
const int N = 10000;
int* a1 = (int*)malloc(sizeof(int)*N);
int* a2 = (int*)malloc(sizeof(int)*N);
int* a3 = (int*)malloc(sizeof(int)*N);
int* a4 = (int*)malloc(sizeof(int)*N);
int* a5 = (int*)malloc(sizeof(int)*N);
int* a6 = (int*)malloc(sizeof(int)*N);
int* a7 = (int*)malloc(sizeof(int)*N);
    
for (int i = 0; i < N; ++i)
 {
    a1[i] = rand();
    a2[i] = a1[i];
    a3[i] = a1[i];
    a4[i] = a1[i];
    a5[i] = a1[i];
    a6[i] = a1[i];
    a7[i] = a1[i];
 }
 int begin1 = clock();
 InsertSort(a1, N);
 int end1 = clock();
    
 int begin2 = clock();
 ShellSort(a2, N);
 int end2 = clock();
    
 int begin3 = clock();
 SelectSort(a3, N);
 int end3 = clock();
    
 int begin4 = clock();
 HeapSort(a4, N);
 int end4 = clock();
    
 int begin5 = clock();
 QuickSort(a4, 0, N-1);
 int end5 = clock();
    
 int begin6 = clock();
 MergeSort(a6, N);
 int end6 = clock();
 printf("InsertSort:%d\n", end1 - begin1);
 printf("ShellSort:%d\n", end2 - begin2);
 printf("SelectSort:%d\n", end3 - begin3);
 printf("HeapSort:%d\n", end4 - begin4);
 printf("QuickSort:%d\n", end5 - begin5);
 printf("MergeSort:%d\n", end6 - begin6);
 free(a1);
 free(a2);
 free(a3);
 free(a4);
 free(a5);
 free(a6);
 free(a7);
 }

結果如下:

請添加圖片描述

6.2 各個排序的穩定性如何判斷?

直接看排完序后是否能保證相同的值的相對位置不會發生變化,若能保證,就是穩定,反之即不穩定,

不要死記,一定要理解分析!

冒泡排序:倆倆對比,前一個大于后一個才發生交換(升序),不會出現相等值互換順序的情況,能保證不改變相同值的相對順序,穩定,

簡單選擇排序:在進行倆數交換位置的程序當中,可能陣列當中有一個數跟發生交換的倆數數值是一樣的,這樣就改變的相同數之間的相對順序,不穩定,

直接插入排序:從前到后一個個元素拿出來跟前面的對比,若插入的數值比被對比的數值小,被對比的數值往后挪動;若插入的數值比被對比的數值大,直接插入到被對比數值的后面,并沒有改變倆個相同值得相對順序,穩定,

希爾排序:在預排序時,相同的資料可能在不同的組里面,沒辦法控制,所以不穩定,

堆排序:比如倆個一樣大的數值,一個在“樹頂”,一個在“樹中”,樹頂元素跟最后一個元素發生交換立馬影響相同數值的相對順序,不穩定,

歸并排序:能保證相同值得相對順序不變,穩定,

快速排序:比如陣列中存在跟key數值一樣的值,而key是肯定會移動的,這樣相對順序就改變了,所以不穩定,

計數排序:計數是在統計每個數出現的次數,但是相同的數哪個在前哪個在后,并沒有區分,所以不穩定,

補充:穩定性有什么意義?

比如我們做了一個考試系統,考生當中先交卷的,成績在陣列的前面,后交卷的,成績在陣列后面,當我們對前幾名進行排名的時候,就可能會遇見倆個分值相同的考生,這時候為了公平性考試用時較短者應當在前面,

6.3八大排序時間/空間復雜度一覽

image-20211017193628979

資料結構八大排序演算法到此介紹結束了,感謝您的閱讀!!!如果內容對你有幫助的話,記得給我三連(點贊、收藏、關注)——做個手有余香的人,


轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/323431.html

標籤:java

上一篇:爆肝20000字,小白零基礎入門,簡歷上的專案經驗該怎么寫?(SpringBoot版,建議收藏)

下一篇:CGBTN2109-DAY10總結復習

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more