主頁 > 後端開發 > 演算法給小碼農八大排序 八奇計只為寶兒姐

演算法給小碼農八大排序 八奇計只為寶兒姐

2021-12-11 07:54:45 後端開發

文章目錄

  • 八排 八奇跡
  • 排序
    • 排序的概念及其運用
      • 排序的概念
      • 排序運用
        • ==來上京東==
        • ==大學排名==
    • 常見的排序演算法
    • 常見排序演算法的實作
        • 插入排序
          • 基本思想
          • ==但是陣列肯定不是有序的,所以我們得先讓陣列有序==
        • 先把列印陣列給剝離出來
        • 插入排序
        • 希爾排序( 縮小增量排序 ) (反正希爾牛逼)
          • 希爾排序步驟
            • ==**單組多躺**==
            • ==**多組插入**==
            • ==**多組一鍋燉(要是分組插麻煩我們也可以一鍋燉)**==
            • ==**多次預排序(gap > 1)+直接插入(gap == 1)**==
  • 測直接插入排序和希爾排序的性能(讓你看看什么才叫希爾排序)
    • 堆的概念及結構
      • 堆的性質
      • 堆的結構(這里實作大堆)
        • 堆的結構體
        • 堆初始化函式HeapInit
        • 堆銷毀函式HeapDestroy
        • 堆列印函式HeapPrint
        • 向上調整函式AdjustUp
        • 堆插入函式HeapPush
        • 判斷堆是否為空函式HeapErmpy
        • 回傳堆大小函式HeapSize
        • 交換函式Swap
        • 向下調整函式AdjustDown
        • 堆洗掉函式HeapPop
  • 排序
    • 常見的排序演算法
    • 常見排序演算法的實作
      • 選擇排序 ==最慢排序(最好理解)所以搬血==
        • 基本思想:
        • 直接選擇排序
        • 資料交換 ==剝離出來其他函式也會用到 我明明是簡潔之人為了一時的高級而忘記了樸素罪過罪過==
        • 選擇排序
      • 堆排序
        • 向下調整函式
        • 堆排序代碼
    • 測性能 ==讓你看看什么叫堆==
      • ==1000大小陣列 一千==
      • ==10000大小陣列 一萬==
      • ==100000大小陣列 十萬==
      • ==1000000大小陣列 一百萬==
      • ==10000000大小陣列 一千萬 我們不帶選擇,插入玩太拉跨了,我們看看希爾,堆在超大資料面前誰性能更優==
    • 性能函式圖
  • 排序
    • 常見的排序演算法
    • 常見排序演算法的實作
      • 歸并排序
        • 基本思想
        • 遞回寫法
          • 通過除錯看一下現象
          • 歸并順序
        • 歸并排序遞回子函式
        • 歸并排序遞回實作
        • 非遞回寫法
          • 2^n^個元素的陣列
          • 隨便幾個元素的陣列
            • 修正下標
        • 歸并排序非遞回實作 修正下標
            • 歸一部分拷一部分
        • 歸并排序非遞回實作 歸一部分拷一部分
        • 歸并排序的特性總結
        • 時間復雜度
      • 測性能
        • 1000 一千
        • 10000 一萬 ==先拋棄選擇和冒泡==
        • 100000 十萬 ==再拋棄直接插入==
        • 1000000 一百萬
        • 10000000 一千萬
  • 排序
    • 常見的排序演算法 擴展
      • 計數排序 不進行資料的比較,而是統計資料出現的次數
        • 計數排序
        • 計數排序的特性總結
      • 測性能
        • 1000 一千
        • 10000 一萬
        • 100000 十萬
        • 1000000 一百萬
        • 10000000 一千萬
    • 排序總結
        • 穩定性
      • 八大排序總結
      • 代碼
        • Sort.h
        • Sort.c
        • test.c

在這里插入圖片描述

八排 八奇跡

排序

排序的概念及其運用

排序的概念

==排序:==所謂排序,就是使一串記錄,按照其中的某個或某些關鍵字的大小,遞增或遞減的排列起來的操作,
==穩定性:==假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,則稱這種排序演算法是穩定的;否則稱為不穩定的,
==內部排序:==資料元素全部放在記憶體中的排序,
==外部排序:==資料元素太多不能同時放在記憶體中,根據排序程序的要求不能在內外存之間移動資料的排序,

排序運用

來上京東

image-20211119074502898

image-20211119075348810

大學排名

image-20211119081356918

常見的排序演算法

image-20211119082822804

常見排序演算法的實作

插入排序

基本思想

直接插入排序是一種簡單的插入排序法,其基本思想是:把待排序的記錄按其關鍵碼值的大小逐個插入到一個已經排好序的有序序列中,直到所有的記錄插入完為止,得到一個新的有序序列 ,

實際中我們玩撲克牌時,就用了插入排序的思想

image-20211119083748963

但是陣列肯定不是有序的,所以我們得先讓陣列有序

image-20211119104919792

先把列印陣列給剝離出來

// 列印陣列
void PrintArray(int* a, int n) {
	assert(a);
	int i = 0;
	for (i = 0; i < n; i++) {
		printf("%d ", a[i]);
	}
	printf("\n");
}

插入排序

// 插入排序
void InsertSort(int* a, int n) {
	assert(a);
	int i = 0;
	for (i = 0; i < n - 1; i++)	{
		int end = i;
		int x = a[end+1];
		while (end >= 0) {
			//要插入的數比順序中的數小就準備挪位置
			if (a[end] > x) {
				a[end + 1] = a[end];
				end--;
			}
			else {
				//插入的數比順序中的要大就跳出
				break;
			}
		}
		//跳出來兩種情況
		//1.end == -1 的時候
		//2.break 的時候
		//把x給end前面一位
		a[end + 1] = x;
	}
}

插入排序的時間復雜度:O(N2)

最好:O(N) — 順序有序 (接近有序)

最壞:O(N2) — 逆序

插入排序的空間復雜度:O(1)

希爾排序( 縮小增量排序 ) (反正希爾牛逼)

希爾排序是在優化直接插入排序,而且效果超級明顯,為什么是優化呢,因為我們知道直接插入排序接近有序了就會非常快,那我就創造這樣的有序,讓他時間復雜度接近O(N),我們知道排序的時間復雜度最好情況就是O(N),而我們接近O(N)也是相當了不起了,基本是接近天花板了

希爾排序法又稱縮小增量法,希爾排序法的基本思想是:先選定一個整數,把待排序檔案中所有記錄分成個組,所有距離為的記錄分在同一組內,并對每一組內的記錄進行排序,然后,取,重復上述分組和排序的作業,當到達=1時,所有記錄在統一組內排好序,

image-20211119112337295

希爾排序步驟

1.分組預排序 ---- 陣列接近有序

按gap分組,對分組值進行插入排序 分成gap組

2.直接插入排序 陣列接近有序,直接插入的時間復雜度就是O(N)

單組多躺

image-20211119184713457

多組插入

image-20211119195005253

間距為gap多組預排實作的時間復雜度O(gap*(1+…+N/gap))

最好:O(N)

最好:O(N)

最壞:O(gap*(1+…+N/gap))

gap越大,預排越快,預排后越不接近有序

gap越小,預排越慢,預排后越接近有序

多組一鍋燉(要是分組插麻煩我們也可以一鍋燉)

image-20211119202112052

多次預排序(gap > 1)+直接插入(gap == 1)

gap/2

image-20211119211913746

gap/3

image-20211119213858518

時間復雜度O(N1.3)記住就行,反正記住希爾很牛逼就行,希爾排序很快

測直接插入排序和希爾排序的性能(讓你看看什么才叫希爾排序)

image-20211119221646761

文章目錄

  • 八排 八奇跡
  • 排序
    • 排序的概念及其運用
      • 排序的概念
      • 排序運用
        • ==來上京東==
        • ==大學排名==
    • 常見的排序演算法
    • 常見排序演算法的實作
        • 插入排序
          • 基本思想
          • ==但是陣列肯定不是有序的,所以我們得先讓陣列有序==
        • 先把列印陣列給剝離出來
        • 插入排序
        • 希爾排序( 縮小增量排序 ) (反正希爾牛逼)
          • 希爾排序步驟
            • ==**單組多躺**==
            • ==**多組插入**==
            • ==**多組一鍋燉(要是分組插麻煩我們也可以一鍋燉)**==
            • ==**多次預排序(gap > 1)+直接插入(gap == 1)**==
  • 測直接插入排序和希爾排序的性能(讓你看看什么才叫希爾排序)
    • 堆的概念及結構
      • 堆的性質
      • 堆的結構(這里實作大堆)
        • 堆的結構體
        • 堆初始化函式HeapInit
        • 堆銷毀函式HeapDestroy
        • 堆列印函式HeapPrint
        • 向上調整函式AdjustUp
        • 堆插入函式HeapPush
        • 判斷堆是否為空函式HeapErmpy
        • 回傳堆大小函式HeapSize
        • 交換函式Swap
        • 向下調整函式AdjustDown
        • 堆洗掉函式HeapPop
  • 排序
    • 常見的排序演算法
    • 常見排序演算法的實作
      • 選擇排序 ==最慢排序(最好理解)所以搬血==
        • 基本思想:
        • 直接選擇排序
        • 資料交換 ==剝離出來其他函式也會用到 我明明是簡潔之人為了一時的高級而忘記了樸素罪過罪過==
        • 選擇排序
      • 堆排序
        • 向下調整函式
        • 堆排序代碼
    • 測性能 ==讓你看看什么叫堆==
      • ==1000大小陣列 一千==
      • ==10000大小陣列 一萬==
      • ==100000大小陣列 十萬==
      • ==1000000大小陣列 一百萬==
      • ==10000000大小陣列 一千萬 我們不帶選擇,插入玩太拉跨了,我們看看希爾,堆在超大資料面前誰性能更優==
    • 性能函式圖
  • 排序
    • 常見的排序演算法
    • 常見排序演算法的實作
      • 歸并排序
        • 基本思想
        • 遞回寫法
          • 通過除錯看一下現象
          • 歸并順序
        • 歸并排序遞回子函式
        • 歸并排序遞回實作
        • 非遞回寫法
          • 2^n^個元素的陣列
          • 隨便幾個元素的陣列
            • 修正下標
        • 歸并排序非遞回實作 修正下標
            • 歸一部分拷一部分
        • 歸并排序非遞回實作 歸一部分拷一部分
        • 歸并排序的特性總結
        • 時間復雜度
      • 測性能
        • 1000 一千
        • 10000 一萬 ==先拋棄選擇和冒泡==
        • 100000 十萬 ==再拋棄直接插入==
        • 1000000 一百萬
        • 10000000 一千萬
  • 排序
    • 常見的排序演算法 擴展
      • 計數排序 不進行資料的比較,而是統計資料出現的次數
        • 計數排序
        • 計數排序的特性總結
      • 測性能
        • 1000 一千
        • 10000 一萬
        • 100000 十萬
        • 1000000 一百萬
        • 10000000 一千萬
    • 排序總結
        • 穩定性
      • 八大排序總結
      • 代碼
        • Sort.h
        • Sort.c
        • test.c

資料結構中的堆不同于作業系統中的堆(作業系統中的堆是用來存盤動態記憶體的),資料結構中的堆是資料的存盤方式,資料結構中的堆是完全二叉樹

既然堆是完全二叉樹的形式存盤的那么就非常適合用陣列的方式來表示

堆的概念及結構

如果有一個關鍵碼的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉樹的順序存盤方式存盤在一個一維陣列中,并滿足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,則稱為小堆(或大堆),將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆,

大堆:樹中一個樹及子樹中,任何一個父親都大于等于孩子

小堆:樹中一個樹及子樹中,任何一個父親都小于等于孩子

堆的性質

堆中某個節點的值總是不大于或不小于其父節點的值;
堆總是一棵完全二叉樹**

堆的結構(這里實作大堆)

image-20211108081113661

既然還是陣列的結構的話就還是順序表的處理方式,陣列指標,size,capacity,雖然物理上我們是用順序表的方式來表示,但是他實際上表示的資料是完全二叉樹,

堆的結構體

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;

堆初始化函式HeapInit

就是一個指向NULL的陣列,size 和 capacity都為零

//堆初始化函式
void HeapInit(HP* hp)
{
	assert(hp);
	hp->a = NULL;
	hp->size = hp->capacity = 0;
}

堆銷毀函式HeapDestroy

由于陣列是動態開辟的,所以用完后需要銷毀的,不然會記憶體泄漏

//堆銷毀函式
void HeapDestroy(HP* hp)
{
	assert(hp);
	free(hp->a);
	hp->size = hp->capacity = 0;
}

堆列印函式HeapPrint

可以想象成一種快速除錯,類似于單片機中的串口列印看資料收發情況

//堆列印函式
void HeapPrint(HP* hp)
{
	int i = 0;
	for (i = 0; i < hp->size; i++)
	{
		printf("%d ", hp->a[i]);
	}
	printf("\n");
}

向上調整函式AdjustUp

為了不影響資料的存盤形式(大堆還得是大堆),插入資料就不能破壞大堆的形式,我們需要把堆插入函式中的資料調整給剝離出來

我們可以看到插入的這個資料對其他的節點并沒有什么影響,有影響的只是這個節點到根這條路徑上的節點,如何解決對這條路徑的影響呢,我們可以形象的看到僅僅是在這條路徑上進行向上調整

通過parent = (child-1)/2 找到父親節點,與之進行比較,然后父親小就交換位置(大堆),然后交換后就在找上面的父親節點,直到找到父親大于孩子,就不交換了

image-20211108115306980

//向上調整函式
void AdjustUp(HPDataType* a, int child)
{
	assert(a);
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (a[parent] < a[child])//父親小于孩子就交換(大堆)
		{
			a[parent] = a[parent] ^ a[child];
			a[child] = a[parent] ^ a[child];
			a[parent] = a[parent] ^ a[child];
			//交換好后重新稱呼一下孩子與父親
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}

	}
}

堆插入函式HeapPush

image-20211108163302928

//堆插入函式(要保持原來形式,大堆還是大堆,小堆就還是小堆)
void HeapPush(HP* hp, HPDataType x)
{
	assert(hp);
	//判斷擴容
	if (hp->size == hp->capacity)
	{
		//容量給新的容量
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		//擴容
		HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);
		//增容失敗
		if (!tmp)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		//增容成功
		hp->a = tmp;
		hp->capacity = newcapacity;		
	}
	//放資料
	hp->a[hp->size] = x;
	hp->size++;
	//實作大堆
	//這個部分的向上調整其他地方也用的到就把他剝離出來
	AdjustUp(hp->a, hp->size - 1);//child下標
}

判斷堆是否為空函式HeapErmpy

//判斷堆是否為空函式
bool HeapErmpy(HP* hp)
{
	assert(hp);
	return hp->size == 0;
}

回傳堆大小函式HeapSize

//回傳堆大小函式
int HeapSize(HP* hp)
{
	assert(hp);
	return hp->size;
}

下面還會用到交換函式,上面也有那么我們不妨把他剝離出來封裝一下,就不需要重復寫了

交換函式Swap

//交換函式void Swap(HPDataType* px, HPDataType* py){	*px = *px ^ *py;	*py = *px ^ *py;	*px = *px ^ *py;}

向下調整函式AdjustDown

image-20211108223143528

//向下調整函式void AdjustDown(HPDataType* a, int size, int parent){	assert(a);	//創建一個孩子變數,有兩個孩子就在這個上加1就行	int child = parent * 2 + 1;	while (child< size)	{		//選大孩子		if (child + 1 < size && a[child] < a[child + 1])		{			child++;		}		//大的孩子還大于父親就交換		if (a[child] > a[parent])		{			Swap(&a[child], &a[parent]);			parent = child;			child = parent * 2 + 1;		}	}}

堆洗掉函式HeapPop

我們可以認為假想根和堆的最后一個元素交換后,把最后一個洗掉,然后再對堆進行操作,你會發現,我們沒有破壞原來的整體結構

image-20211108223334078

//堆洗掉函式(洗掉的是堆頂資料也就是取最值)
//還有不可能一直刪的所以我們需要一個判空函式
void HeapPop(HP* hp)
{
	assert(hp);
	assert(!HeapErmpy(hp));
	//根和堆最后一個元素交換
	Swap(&hp->a[0],&hp->a[hp->size-1]);
	//把最后一個洗掉,就是我們想要洗掉的元素
	hp->size--;
	//向下調整
	AdjustDown(hp->a,hp->size,0);
}

文章目錄

  • 八排 八奇跡
  • 排序
    • 排序的概念及其運用
      • 排序的概念
      • 排序運用
        • ==來上京東==
        • ==大學排名==
    • 常見的排序演算法
    • 常見排序演算法的實作
        • 插入排序
          • 基本思想
          • ==但是陣列肯定不是有序的,所以我們得先讓陣列有序==
        • 先把列印陣列給剝離出來
        • 插入排序
        • 希爾排序( 縮小增量排序 ) (反正希爾牛逼)
          • 希爾排序步驟
            • ==**單組多躺**==
            • ==**多組插入**==
            • ==**多組一鍋燉(要是分組插麻煩我們也可以一鍋燉)**==
            • ==**多次預排序(gap > 1)+直接插入(gap == 1)**==
  • 測直接插入排序和希爾排序的性能(讓你看看什么才叫希爾排序)
    • 堆的概念及結構
      • 堆的性質
      • 堆的結構(這里實作大堆)
        • 堆的結構體
        • 堆初始化函式HeapInit
        • 堆銷毀函式HeapDestroy
        • 堆列印函式HeapPrint
        • 向上調整函式AdjustUp
        • 堆插入函式HeapPush
        • 判斷堆是否為空函式HeapErmpy
        • 回傳堆大小函式HeapSize
        • 交換函式Swap
        • 向下調整函式AdjustDown
        • 堆洗掉函式HeapPop
  • 排序
    • 常見的排序演算法
    • 常見排序演算法的實作
      • 選擇排序 ==最慢排序(最好理解)所以搬血==
        • 基本思想:
        • 直接選擇排序
        • 資料交換 ==剝離出來其他函式也會用到 我明明是簡潔之人為了一時的高級而忘記了樸素罪過罪過==
        • 選擇排序
      • 堆排序
        • 向下調整函式
        • 堆排序代碼
    • 測性能 ==讓你看看什么叫堆==
      • ==1000大小陣列 一千==
      • ==10000大小陣列 一萬==
      • ==100000大小陣列 十萬==
      • ==1000000大小陣列 一百萬==
      • ==10000000大小陣列 一千萬 我們不帶選擇,插入玩太拉跨了,我們看看希爾,堆在超大資料面前誰性能更優==
    • 性能函式圖
  • 排序
    • 常見的排序演算法
    • 常見排序演算法的實作
      • 歸并排序
        • 基本思想
        • 遞回寫法
          • 通過除錯看一下現象
          • 歸并順序
        • 歸并排序遞回子函式
        • 歸并排序遞回實作
        • 非遞回寫法
          • 2^n^個元素的陣列
          • 隨便幾個元素的陣列
            • 修正下標
        • 歸并排序非遞回實作 修正下標
            • 歸一部分拷一部分
        • 歸并排序非遞回實作 歸一部分拷一部分
        • 歸并排序的特性總結
        • 時間復雜度
      • 測性能
        • 1000 一千
        • 10000 一萬 ==先拋棄選擇和冒泡==
        • 100000 十萬 ==再拋棄直接插入==
        • 1000000 一百萬
        • 10000000 一千萬
  • 排序
    • 常見的排序演算法 擴展
      • 計數排序 不進行資料的比較,而是統計資料出現的次數
        • 計數排序
        • 計數排序的特性總結
      • 測性能
        • 1000 一千
        • 10000 一萬
        • 100000 十萬
        • 1000000 一百萬
        • 10000000 一千萬
    • 排序總結
        • 穩定性
      • 八大排序總結
      • 代碼
        • Sort.h
        • Sort.c
        • test.c

排序

常見的排序演算法

image-20211119082822804

常見排序演算法的實作

選擇排序 最慢排序(最好理解)所以搬血

基本思想:

每一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的資料元素排完 ,

直接選擇排序

在元素集合array[i]–array[n-1]中選擇關鍵碼最大(小)的資料元素
若它不是這組元素中的最后一個(第一個)元素,則將它與這組元素中的最后一個(第一個)元素交換
在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重復上述步驟,直到集合剩余1個元素

image-20211120195032904

上面那個就是選擇排序的本質,但是一次就選一個最大或者最小是不是有點浪費,我們一次同時選到最大最小,就是會比傳統的選擇排序快一倍

我們基本看到上面代碼的缺陷就是我們第一個就是最大是時候,最大的就被換走了,而最小的就被換過來了,但是最大的下標還是標記首位置,把最小的換到后面,也就出現了最小的1在后面的現象

解決方法:既然你最大數的下標和begin重合,那最大數被換走的時候,maxi這個下標也要連帶著走

image-20211120233139638

實際上下面 才是我第一次寫的代碼,直接說下次我再也不寫裝逼的交換了

image-20211120235444317

我來道bug惡心之處 看好了跳跳 5 ^ 5 == 0 這就是惡心之處,下次再也不裝逼了

資料交換 剝離出來其他函式也會用到 我明明是簡潔之人為了一時的高級而忘記了樸素罪過罪過

//資料交換
void Swap(int* pa, int* pb) {
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

選擇排序

// 選擇排序
void SelectSort(int* a, int n) {
	int begin = 0;
	int end = n - 1;
	while (begin < end){
		//單趟
		//最大數,最小數的下標
		int mini = begin;//這邊假設是剛開始的下標
		int maxi = end;  //這邊假設是末尾的下標
		int i = 0;
		for (i = begin; i <= end; i++) {
			if (a[i] < a[mini])
				mini = i;
			if (a[i] > a[maxi])
				maxi = i;
		}
		//最小的放前面
		Swap(&a[begin], &a[mini]);
		
		if (begin == maxi)
			//如果最大數就是begin位置的,那么交換的時候最大數連帶著下標一起動
			maxi = mini;
		//最大的放后面
		Swap(&a[end], &a[maxi]);
		begin++;
		end--;
	}
}

時間復雜度是O(N2) 我們的優化不是質的優化,而是量的優化

最好:O(N2)

最壞:O(N2)

堆排序

堆排序(Heapsort)是指利用堆積樹(堆)這種資料結構所設計的一種排序演算法,它是選擇排序的一種,它是
通過堆來進行選擇資料,需要注意的是排升序要建大堆,排降序建小堆,

image-20211121094727004

向下調整函式

//向下調整函式
void AdjustDown(int* a, int n, int parent)
{
	assert(a);
	//創建一個孩子變數,有兩個孩子就在這個上加1就行
	int child = parent * 2 + 1;
#if HEAP
	while (child < n)
	{
		//選大孩子
		if (child + 1 < n && a[child] < a[child + 1])
		{
			child++;
		}
		//大的孩子還大于父親就交換
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
#elif !HEAP
	while (child < n)
	{
		//選小孩子
		if (child + 1 < n && a[child] > a[child + 1])
		{
			child++;
		}
		//小的孩子還小于父親就交換
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
#endif // HEAP	
}

堆排序代碼

// 堆排序   我們之前講過升序建大堆
void HeapSort(int* a, int n) {
	//建堆時間復雜度O(N)
	//建大堆
	int i = 0;
	for (i = (n - 1 - 1) / 2; i >= 0; i--) {
		AdjustDown(a, n, i);
	}
	int end = n - 1;
	//堆排序時間復雜度O(N*logN)
	while (end>0){
		//交換 把最大的放到后面
		Swap(&a[0], &a[end]);
		//在向下調整
		AdjustDown(a,end,0);
		end--;
	}
}

堆排序時間復雜度O(N*logN)

測性能 讓你看看什么叫堆

這里我們測性能就用release版本測吧 因為release版本是程式最優狀態,每個排序都是最好狀態,巔峰打巔峰

1000大小陣列 一千

image-20211121113727817

10000大小陣列 一萬

image-20211121114331200

100000大小陣列 十萬

image-20211121114552970

1000000大小陣列 一百萬

image-20211121125949374

10000000大小陣列 一千萬 我們不帶選擇,插入玩太拉跨了,我們看看希爾,堆在超大資料面前誰性能更優

image-20211121130941961

性能函式圖

image-20211121133907018

文章目錄

  • 八排 八奇跡
  • 排序
    • 排序的概念及其運用
      • 排序的概念
      • 排序運用
        • ==來上京東==
        • ==大學排名==
    • 常見的排序演算法
    • 常見排序演算法的實作
        • 插入排序
          • 基本思想
          • ==但是陣列肯定不是有序的,所以我們得先讓陣列有序==
        • 先把列印陣列給剝離出來
        • 插入排序
        • 希爾排序( 縮小增量排序 ) (反正希爾牛逼)
          • 希爾排序步驟
            • ==**單組多躺**==
            • ==**多組插入**==
            • ==**多組一鍋燉(要是分組插麻煩我們也可以一鍋燉)**==
            • ==**多次預排序(gap > 1)+直接插入(gap == 1)**==
  • 測直接插入排序和希爾排序的性能(讓你看看什么才叫希爾排序)
    • 堆的概念及結構
      • 堆的性質
      • 堆的結構(這里實作大堆)
        • 堆的結構體
        • 堆初始化函式HeapInit
        • 堆銷毀函式HeapDestroy
        • 堆列印函式HeapPrint
        • 向上調整函式AdjustUp
        • 堆插入函式HeapPush
        • 判斷堆是否為空函式HeapErmpy
        • 回傳堆大小函式HeapSize
        • 交換函式Swap
        • 向下調整函式AdjustDown
        • 堆洗掉函式HeapPop
  • 排序
    • 常見的排序演算法
    • 常見排序演算法的實作
      • 選擇排序 ==最慢排序(最好理解)所以搬血==
        • 基本思想:
        • 直接選擇排序
        • 資料交換 ==剝離出來其他函式也會用到 我明明是簡潔之人為了一時的高級而忘記了樸素罪過罪過==
        • 選擇排序
      • 堆排序
        • 向下調整函式
        • 堆排序代碼
    • 測性能 ==讓你看看什么叫堆==
      • ==1000大小陣列 一千==
      • ==10000大小陣列 一萬==
      • ==100000大小陣列 十萬==
      • ==1000000大小陣列 一百萬==
      • ==10000000大小陣列 一千萬 我們不帶選擇,插入玩太拉跨了,我們看看希爾,堆在超大資料面前誰性能更優==
    • 性能函式圖
  • 排序
    • 常見的排序演算法
    • 常見排序演算法的實作
      • 歸并排序
        • 基本思想
        • 遞回寫法
          • 通過除錯看一下現象
          • 歸并順序
        • 歸并排序遞回子函式
        • 歸并排序遞回實作
        • 非遞回寫法
          • 2^n^個元素的陣列
          • 隨便幾個元素的陣列
            • 修正下標
        • 歸并排序非遞回實作 修正下標
            • 歸一部分拷一部分
        • 歸并排序非遞回實作 歸一部分拷一部分
        • 歸并排序的特性總結
        • 時間復雜度
      • 測性能
        • 1000 一千
        • 10000 一萬 ==先拋棄選擇和冒泡==
        • 100000 十萬 ==再拋棄直接插入==
        • 1000000 一百萬
        • 10000000 一千萬
  • 排序
    • 常見的排序演算法 擴展
      • 計數排序 不進行資料的比較,而是統計資料出現的次數
        • 計數排序
        • 計數排序的特性總結
      • 測性能
        • 1000 一千
        • 10000 一萬
        • 100000 十萬
        • 1000000 一百萬
        • 10000000 一千萬
    • 排序總結
        • 穩定性
      • 八大排序總結
      • 代碼
        • Sort.h
        • Sort.c
        • test.c

排序

常見的排序演算法

image-20211119082822804

常見排序演算法的實作

歸并排序

基本思想

歸并排序(MERGE-SORT)是建立在歸并操作上的一種有效的排序演算法,該演算法是采用分治法(Divide and Conquer)的一個非常典型的應用,將已有序的子序列合并,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序,若將兩個有序表合并成一個有序表,稱為二路歸并, 歸并排序核心步驟:

實際上歸并我們不是第一次接觸,之前我們也是接觸過的,比如合并兩個有序陣列這個就是歸并思想

但是我們上面的題目是左區間有序,右區間也有序,我們正常題目肯定不會直接給你有序,這時候再深一點,你不是沒有序嗎,那我們再分,分到你無法再分,也就是只有一個了,你能說一個沒有序嗎,肯定不行,所以我們繼續分治,

遞回寫法

看上面的GIF也知道第一反應是遞回

通過除錯看一下現象

image-20211130224907672

歸并順序

image-20211130230648998

歸并2

image-20211201014452321

歸并排序遞回子函式

// 歸并排序遞回子函式
void _MergeSort(int* a, int left, int right, int* tmp){
	//左大于右說明是空陣列,空陣列就跳
	//左等于右就是我們要的單體有序
	if (left >= right)
		return;
	//防溢位寫法
	int mid = left + (right - left) / 2;
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid+1,right, tmp);
	//
	int begin1 = left;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = right;
	int i = left;
	//跑空一組就直接跳
	while (begin1<=end1 && begin2<=end2){
		if (a[begin1] < a[begin2]) {
			tmp[i++] = a[begin1++];
		}			
		else {
			tmp[i++] = a[begin2++];
		}
	}
	while (begin1 <= end1) {
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2) {
		tmp[i++] = a[begin2++];
	}
	//把tmp陣列拷貝回到原來的陣列中
	i = left;
	while (i<=right)
	{
		a[i] = tmp[i];
		i++;
	}
}

歸并排序遞回實作

// 歸并排序遞回實作
void MergeSort(int* a, int n) {
	assert(a);
	//首先創建一個臨時陣列
	int* tmp = (int*)malloc(sizeof(int) * n);
	//空就直接錯
	assert(tmp);
	//子函式
	_MergeSort(a, 0, n - 1, tmp);
	//不用了就free掉
	free(tmp);
	//然后置空
	tmp = NULL;
}

非遞回寫法

2n個元素的陣列

image-20211201012824875

image-20211201014637727

我們看到上面好像沒啥問題,那是用為陣列元素個數真的太有好了,一直沒有落單的元素,好的不真實

image-20211201015555785

image-20211201020545678

隨便幾個元素的陣列
修正下標

越界情況討論

image-20211201092011579

但是出現另一種惡心情況 重復拷貝

image-20211201094210650

所以接下來我們需要解決index問題

image-20211201095510524

我們修正到n-1,同樣也可以把陣列修不存在,讓他不進下面的回圈也就可以不會進行歸并

image-20211201100753211

image-20211201101244258

歸并排序非遞回實作 修正下標

// 歸并排序非遞回實作
void MergeSortNonR(int* a, int n) {
	assert(a);
	//首先創建一個臨時陣列
	int* tmp = (int*)malloc(sizeof(int) * n);
	//空就直接錯
	assert(tmp);
	int gap = 1;
	int i = 0;
	while (gap<n){
		for (i = 0; i < n; i += 2 * gap){
			//單組需要排序的區間
			//[i,i+gap-1]  [i+gap,i+2*gap-1]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i+gap, end2 = i + 2*gap - 1;
			//適用任何元素個數的核心部分
			//end1出界,[begin2,end2]不存在
			if (end1 >= n) {
				end1 = n - 1;
			}
			//[begin2,end2]不存在
			if (begin2 >= n) {
				begin2 = n ;
				end2 = n - 1;
			}
			//end2出界
			if (end2 >= n) {
				end2 = n - 1;
			}
			//printf("[%d,%d],[%d,%d]",begin1,end1,begin2,end2);
			重復拷貝基本是我們修正到同一個位置的原因
			我們條件斷點一下
			//if (begin1 == end1 && end1 == begin2 && begin2 == end2 && end2 == n-1)
			//{
			//	//隨便一個代碼來承接斷點,一句費代碼
			//	int a = 0;
			//}
			
			//tmp需要一個索引
			int index = i;
			while (begin1 <= end1 && begin2 <= end2){
				if (a[begin1] > a[begin2]) {
					tmp[index++] = a[begin2++];
				}
				else{
					tmp[index++] = a[begin1++];
				}
			}			
			//肯定還有一個沒跑完
			while (begin1 <= end1){
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2) {
				tmp[index++] = a[begin2++];
			}		
			//printf("       %d", index);
		}
		//printf("\n");
		
		//一行歸并完了再考回去
		for (i = 0; i < n; i++) {
			a[i] = tmp[i];
		}
		gap *= 2;
	}	
	//不用了就free掉
	free(tmp);
	//然后置空
	tmp = NULL;
}
歸一部分拷一部分

我們也可以像遞回那樣歸一半分拷貝一部分,就不需要修正了,因為修正要考慮很多邊界情況,有點繁瑣

image-20211201104303020

歸并排序非遞回實作 歸一部分拷一部分

// 歸并排序非遞回實作
void MergeSortNonR(int* a, int n) {
	assert(a);
	//首先創建一個臨時陣列
	int* tmp = (int*)malloc(sizeof(int) * n);
	//空就直接錯
	assert(tmp);
	int gap = 1;
	int i = 0;
	while (gap<n){
		for (i = 0; i < n; i += 2 * gap){
			//單組需要排序的區間
			//[i,i+gap-1]  [i+gap,i+2*gap-1]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i+gap, end2 = i + 2*gap - 1;
			適用任何元素個數的核心部分
			end1出界,[begin2,end2]不存在
			//if (end1 >= n) {
			//	end1 = n - 1;
			//}
			[begin2,end2]不存在
			//if (begin2 >= n) {
			//	begin2 = n ;
			//	end2 = n - 1;
			//}
			end2出界
			//if (end2 >= n) {
			//	end2 = n - 1;
			//}
			//適用任何元素個數的核心部分
			//end1出界,[begin2,end2]不存在 都不需要歸并
			if (end1 >= n || begin2 >= n) {
				//直接跳,因為是在原陣列操作的不需要擔心最后一個沒考進去
				break;
			}
			//end2出界  需要歸并  就修正
			if (end2 >= n) {
				end2 = n - 1;
			}
			//printf("[%d,%d],[%d,%d]",begin1,end1,begin2,end2);
			重復拷貝基本是我們修正到同一個位置的原因
			我們條件斷點一下
			//if (begin1 == end1 && end1 == begin2 && begin2 == end2 && end2 == n-1)
			//{
			//	//隨便一個代碼來承接斷點,一句費代碼
			//	int a = 0;
			//}
			
			//tmp需要一個索引
			int index = i;
			while (begin1 <= end1 && begin2 <= end2){
				if (a[begin1] > a[begin2]) {
					tmp[index++] = a[begin2++];
				}
				else{
					tmp[index++] = a[begin1++];
				}
			}
			
			//肯定還有一個沒跑完
			while (begin1 <= end1){
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2) {
				tmp[index++] = a[begin2++];
			}		
			//歸一部分拷貝一部分
			int j = 0;
			for (j = i; j <= end2; j++) {
				a[j] = tmp[j];
			}
			//printf("       %d", index);
		}
		//printf("\n");
		
		一行歸并完了再考回去
		//for (i = 0; i < n; i++) {
		//	a[i] = tmp[i];
		//}
		gap *= 2;
	}	
	//不用了就free掉
	free(tmp);
	//然后置空
	tmp = NULL;
}

歸并排序的特性總結

  1. 歸并的缺點在于需要O(N)的空間復雜度,歸并排序的思考更多的是解決在磁盤中的外排序問題,
  2. 時間復雜度:O(N*logN)
  3. 空間復雜度:O(N)
  4. 穩定性:穩定

時間復雜度

時間復雜度:O(N*logN)

歸并排序方法就是把一組n個數的序列,折半分為兩個序列,然后再將這兩個序列再分,一直分下去,直到分為n個長度為1的序列,然后兩兩按大小歸并,如此反復,直到最后形成包含n個數的一個陣列,

歸并排序總時間=分解時間+子序列排好序時間+合并時間

無論每個序列有多少數都是折中分解,所以分解時間是個常數,可以忽略不計,

則:歸并排序總時間=子序列排好序時間+合并時間

測性能

1000 一千

image-20211201130928149

10000 一萬 先拋棄選擇和冒泡

image-20211201131123059

100000 十萬 再拋棄直接插入

image-20211201131307252

1000000 一百萬

image-20211201131726053

10000000 一千萬

image-20211201131925057

文章目錄

  • 八排 八奇跡
  • 排序
    • 排序的概念及其運用
      • 排序的概念
      • 排序運用
        • ==來上京東==
        • ==大學排名==
    • 常見的排序演算法
    • 常見排序演算法的實作
        • 插入排序
          • 基本思想
          • ==但是陣列肯定不是有序的,所以我們得先讓陣列有序==
        • 先把列印陣列給剝離出來
        • 插入排序
        • 希爾排序( 縮小增量排序 ) (反正希爾牛逼)
          • 希爾排序步驟
            • ==**單組多躺**==
            • ==**多組插入**==
            • ==**多組一鍋燉(要是分組插麻煩我們也可以一鍋燉)**==
            • ==**多次預排序(gap > 1)+直接插入(gap == 1)**==
  • 測直接插入排序和希爾排序的性能(讓你看看什么才叫希爾排序)
    • 堆的概念及結構
      • 堆的性質
      • 堆的結構(這里實作大堆)
        • 堆的結構體
        • 堆初始化函式HeapInit
        • 堆銷毀函式HeapDestroy
        • 堆列印函式HeapPrint
        • 向上調整函式AdjustUp
        • 堆插入函式HeapPush
        • 判斷堆是否為空函式HeapErmpy
        • 回傳堆大小函式HeapSize
        • 交換函式Swap
        • 向下調整函式AdjustDown
        • 堆洗掉函式HeapPop
  • 排序
    • 常見的排序演算法
    • 常見排序演算法的實作
      • 選擇排序 ==最慢排序(最好理解)所以搬血==
        • 基本思想:
        • 直接選擇排序
        • 資料交換 ==剝離出來其他函式也會用到 我明明是簡潔之人為了一時的高級而忘記了樸素罪過罪過==
        • 選擇排序
      • 堆排序
        • 向下調整函式
        • 堆排序代碼
    • 測性能 ==讓你看看什么叫堆==
      • ==1000大小陣列 一千==
      • ==10000大小陣列 一萬==
      • ==100000大小陣列 十萬==
      • ==1000000大小陣列 一百萬==
      • ==10000000大小陣列 一千萬 我們不帶選擇,插入玩太拉跨了,我們看看希爾,堆在超大資料面前誰性能更優==
    • 性能函式圖
  • 排序
    • 常見的排序演算法
    • 常見排序演算法的實作
      • 歸并排序
        • 基本思想
        • 遞回寫法
          • 通過除錯看一下現象
          • 歸并順序
        • 歸并排序遞回子函式
        • 歸并排序遞回實作
        • 非遞回寫法
          • 2^n^個元素的陣列
          • 隨便幾個元素的陣列
            • 修正下標
        • 歸并排序非遞回實作 修正下標
            • 歸一部分拷一部分
        • 歸并排序非遞回實作 歸一部分拷一部分
        • 歸并排序的特性總結
        • 時間復雜度
      • 測性能
        • 1000 一千
        • 10000 一萬 ==先拋棄選擇和冒泡==
        • 100000 十萬 ==再拋棄直接插入==
        • 1000000 一百萬
        • 10000000 一千萬
  • 排序
    • 常見的排序演算法 擴展
      • 計數排序 不進行資料的比較,而是統計資料出現的次數
        • 計數排序
        • 計數排序的特性總結
      • 測性能
        • 1000 一千
        • 10000 一萬
        • 100000 十萬
        • 1000000 一百萬
        • 10000000 一千萬
    • 排序總結
        • 穩定性
      • 八大排序總結
      • 代碼
        • Sort.h
        • Sort.c
        • test.c

排序

常見的排序演算法 擴展

計數排序 不進行資料的比較,而是統計資料出現的次數

思想:計數排序又稱為鴿巢原理,是對哈希直接定址法的變形應用, 操作步驟

  1. 統計相同元素出現次數
  2. 根據統計的結果將序列回收到原來的序列中
跳動3

跳動5

我們可以發現是不是很快,自我感覺一下,沒錯實際上的確是很快的,時間復雜度是高效到==O(N)==級別的,計數的本質是哈希,所謂的映射

這也會面臨一個很現實的問題,就是前面沒有時候數,后面1000的位置反而有數,咋處理

eg.1000 1100 1200 1300 1200 1400 1000 1500 1300 1200

image-20211202154622236

我們也可以發現計數排序比較適合大小范圍集中的陣列

image-20211202172954777

計數排序

// 計數排序
void CountSort(int* a, int n) {
	assert(a);
	int max = a[0], min = a[0];
	int i = 0;
	for (i = 0; i < n; i++) {
		if (a[i] > max)
			max = a[i];
		if (a[i] < min)
			min = a[i];
	}
	//范圍
	int range = max - min + 1;
	//開辟計數陣列
	int* count = (int*)malloc(sizeof(int) * range);	
	//沒開辟成功直接錯
	assert(count);
	//陣列全部初始化為零
	memset(count, 0, sizeof(int) * range);
	//統計次數
	for (i = 0; i < n; i++) {
		//相對映射加加
		count[a[i] - min]++;
	}
	//通過計數陣列的次數對原陣列進行排序
	int j = 0;
	for (i = 0; i < range; i++) {
		while (count[i]--)
			//相對映射要回到原陣列的資料+min
			a[j++] = i+min;
	}
	free(count);
	count = NULL;
}

計數排序的特性總結

  1. 計數排序在資料范圍集中時,效率很高,但是適用范圍及場景有限,
  2. 時間復雜度:O(MAX(N,范圍))
  3. 空間復雜度:O(范圍)

測性能

1000 一千

image-20211202200417560

10000 一萬

image-20211202200429293

100000 十萬

image-20211202200438305

1000000 一百萬

image-20211202200446230

10000000 一千萬

image-20211202200626193

排序總結

image-20211202204954630

穩定性

假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,r[i] = r[j],且r[i] 在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,則稱這種排序演算法是最穩定的,否則稱為不穩定的

再簡單一點:陣列中相同的值,在排序以后相對位置是否變化

可能會變的,就是不穩定

能保持不變,就是穩定

image-20211202211158189

image-20211202222559952

八大排序總結

排序方法平均時間復雜度最壞時間復雜度最好時間復雜度空間復雜度穩定性
插入排序O(N^2)O(N^2)O(N)O(1)穩定
希爾排序O(N^1.3)O(N^2)O(N)O(1)不穩定
選擇排序O(N^2)O(N^2)O(N^2)O(1)不穩定
堆排序O(n*logN)O(n*logN)O(n*logN)O(1)不穩定
冒泡排序O(N^2)O(N^2)O(N)O(1)穩定
快速排序O(n*logN)O(N^2)O(n*logN)最好O(logN),最壞O(N)不穩定
歸并排序O(n*logN)O(n*logN)O(n*logN)O(N)穩定
計數排序O(MAX(N,range))O(MAX(N,range))O(N)O(range)不穩定

代碼

Sort.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>

#define HEAP        1

// 排序實作的介面
// 列印陣列
extern void PrintArray(int* a, int n);
// 插入排序
extern void InsertSort(int* a, int n);
// 希爾排序
extern void ShellSort(int* a, int n);
//資料交換
extern void Swap(int* pa, int* pb);
// 選擇排序
extern void SelectSort(int* a, int n);
//向下調整
extern void AdjustDwon(int* a, int n, int parent);
// 堆排序
extern void HeapSort(int* a, int n);
// 冒泡排序
extern void BubbleSort(int* a, int n);
// 快速排序遞回實作
// 快速排序hoare版本
extern int PartSort1(int* a, int left, int right);
// 快速排序挖坑法
extern int PartSort2(int* a, int left, int right);
// 快速排序前后指標法
extern int PartSort3(int* a, int left, int right);
extern void QuickSort(int* a, int left, int right);
// 快速排序 非遞回實作
extern void QuickSortNonR(int* a, int left, int right);
// 歸并排序遞回實作
extern void MergeSort(int* a, int n);
// 歸并排序非遞回實作
extern void MergeSortNonR(int* a, int n);
// 計數排序
extern void CountSort(int* a, int n);


Sort.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Sort.h"
#include"Stack.h"

// 列印陣列
void PrintArray(int* a, int n) {
	assert(a);
	int i = 0;
	for (i = 0; i < n; i++) {
		printf("%d ", a[i]);
	}
	printf("\n");
}
// 插入排序
void InsertSort(int* a, int n) {
	assert(a);
	int i = 0;
	for (i = 0; i < n - 1; i++)	{
		int end = i;
		int x = a[end+1];
		while (end >= 0) {
			//要插入的數比順序中的數小就準備挪位置
			if (a[end] > x) {
				a[end + 1] = a[end];
				end--;
			}
			else {
				//插入的數比順序中的要大就跳出
				break;
			}
		}
		//跳出來兩種情況
		//1.end == -1 的時候
		//2.break 的時候
		//把x給end前面一位
		a[end + 1] = x;
	}
}
// 希爾排序
void ShellSort(int* a, int n) {
	//分組
	int gap = n;
	//多次預排序(gap>1)+ 直接插入(gap == 1)
	while (gap>1){
		//gap /= 2;
		//除以三我們知道不一定會過1,所以我們+1讓他有一個必過1的條件
		gap = gap / 3 + 1;
		//單組多躺
		int i = 0;
		for (i = 0; i < n - gap; i++) {
		int end = i;
		int x = a[end + gap];
		while (end >= 0) {
			if (a[end] > x) {
				a[end + gap] = a[end];
				//步長是gap
				end -= gap;
			}
			else {
				break;
			}
		}
		a[end + gap] = x;
	}
	}	
}
//資料交換
void Swap(int* pa, int* pb) {
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}
// 選擇排序
void SelectSort(int* a, int n) {
	int begin = 0;
	int end = n - 1;
	while (begin < end){
		//單趟
		//最大數,最小數的下標
		int mini = begin;//這邊假設是剛開始的下標
		int maxi = end;  //這邊假設是末尾的下標
		int i = 0;
		for (i = begin; i <= end; i++) {
			if (a[i] < a[mini])
				mini = i;
			if (a[i] > a[maxi])
				maxi = i;
		}
		//最小的放前面
		Swap(&a[begin], &a[mini]);
		
		if (begin == maxi)
			//如果最大數就是begin位置的,那么交換的時候最大數連帶著下標一起動
			maxi = mini;
		//最大的放后面
		Swap(&a[end], &a[maxi]);
		begin++;
		end--;
	}
}
//向下調整函式
void AdjustDown(int* a, int n, int parent)
{
	assert(a);
	//創建一個孩子變數,有兩個孩子就在這個上加1就行
	int child = parent * 2 + 1;
#if HEAP
	while (child < n)
	{
		//選大孩子
		if (child + 1 < n && a[child] < a[child + 1])
		{
			child++;
		}
		//大的孩子還大于父親就交換
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
#elif !HEAP
	while (child < n)
	{
		//選小孩子
		if (child + 1 < n && a[child] > a[child + 1])
		{
			child++;
		}
		//小的孩子還小于父親就交換
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
#endif // HEAP	
}
// 堆排序   我們之前講過升序建大堆
void HeapSort(int* a, int n) {
	//建堆時間復雜度O(N)
	//建大堆
	int i = 0;
	for (i = (n - 1 - 1) / 2; i >= 0; i--) {
		AdjustDown(a, n, i);
	}
	int end = n - 1;
	//堆排序時間復雜度O(N*logN)
	while (end>0){
		//交換 把最大的放到后面
		Swap(&a[0], &a[end]);
		//在向下調整
		AdjustDown(a,end,0);
		end--;
	}
}
// 冒泡排序
void BubbleSort(int* a, int n) {
	//多躺
	int j = 0;	
	for (j = 0; j < n - 1; j++) {	
		//交換標記變數
		int flag = 0;
		//單趟
		int i = 0;
		for (i = 0; i < n - 1-j; i++) {			
			if (a[i] > a[i + 1]) {
				//交換標記改變
				flag = 1;
				Swap(&a[i], &a[i + 1]);
			}
		}
		//標記還是0就跳出
		if (!flag)
			break;
	}
}
//三數取中
int GetMinIndex(int* a, int left, int right) {
	//這樣可以防止 int 溢位
	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 left;
		else
			return right;
	}
	else //a[left] >= a[mid]
	{
		if (a[mid] > a[right])
			return mid;
		else if (a[left] < a[right])
			return left;
		else
			return right;
	}
}
// 快速排序hoare版本 單趟排序
//最左邊做key  [left,right]  我們這里給區間
int PartSort1(int* a, int left, int right) {
	//三數取中
	int mini = GetMinIndex(a, left, right);
	//把中間的數放到最左邊,交換即可
	Swap(&a[mini], &a[left]);
	//還是最左邊為keyi
	int keyi = left;
	//左右相遇就停止
	while (left < right)
	{
		//最左邊為key,那么最右邊就先動
		//找小于key的
		while (left < right && a[right] >= a[keyi]) {
			right--;
		}
		//然后再動右邊的
		//找大于key的
		while (left < right && a[left] <= a[keyi]) {
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[keyi], &a[right]);
	//回傳正確位置后的keyi
	return left;
}
// 快速排序挖坑法
int PartSort2(int* a, int left, int right) {
	assert(a);
	//三數取中
	int mini = GetMinIndex(a, left, right);
	//把中間的數放到最左邊,交換即可
	Swap(&a[mini], &a[left]);
	//先把Key存下來
	int Key = a[left];
	//挖坑
	int pit = left;
	while (left<right){
		//右邊找小
		while (left < right && a[right] >= Key) {
			right--;
		}
		//找到后把資料扔到坑里面去
		Swap(&a[right],&a[pit]);
		//自己就變成新的坑
		pit = right;
		//左邊找大
		while (left < right && a[left] <= Key) {
			left++;
		}
		//找到后把資料扔到坑里面去
		Swap(&a[left], &a[pit]);
		//自己就變成新的坑
		pit = left;
	}
	//出來后把Key放到坑里面去
	a[pit] = Key;
	return pit;
}

// 快速排序前后指標法
int PartSort3(int* a, int left, int right) {
	assert(a);	
	//三數取中
	int mini = GetMinIndex(a, left, right);
	//把中間的數放到最左邊,交換即可
	Swap(&a[mini], &a[left]);
	//把keyi記下來
	int keyi = left;
	int prev = left;
	int cur = prev + 1;
	while (cur <= right){
		比Key小就跳出
		//while (cur <= right && a[cur] >= a[keyi]) {
		//	cur++;
		//}
		//if (cur <= right) {
		//	//跳出來prev++
		//	prev++;
		//	//交換
		//	Swap(&a[prev], &a[cur]);
		//	//交換完后cur也++
		//	cur++;
		//}		
		if(a[cur] < a[keyi])
			Swap(&a[prev++], &a[cur]);
		cur++;
	}
	//跳出來說明交換a[prev]和Key
	Swap(&a[prev],&a[keyi]);
	return prev;
}


// 快速排序  小區間優化
void QuickSort(int* a, int left, int right) {
	if (left >= right)
		return;
	if (right - left + 1 < 10)//10以內的數插入
	{
		InsertSort(a + left, right - left + 1);
	}
	else
	{
		int keyi = PartSort3(a, left, right);
		//[left,keyi-1] keyi [keyi+1,right]
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}	
}
// 快速排序 非遞回實作
void QuickSortNonR(int* a, int left, int right) {
	//建堆疊
	ST st;
	//初始化堆疊
	StackInit(&st);
	//left進堆疊
	StackPush(&st, left);
	//right進堆疊
	StackPush(&st, right);
	//空堆疊跳出 
	while (!StackEmpty(&st))
	{
		//先取尾
		int end = StackTop(&st);
		//pop掉
		StackPop(&st);
		//再取頭
		int start = StackTop(&st);
		//再pop掉
		StackPop(&st);

		//然后單趟排序找到keyi
		int keyi = PartSort3(a,start,end);
		//[start,keyi-1] keyi [keyi+1,end]
		if (keyi + 1 < end)//表示分割開來的區間大于1
		{
			//因為我們先取尾,所以問先入頭
			StackPush(&st, keyi + 1);
			//再入尾
			StackPush(&st, end);
		}
		if (keyi - 1 > start)//表示分割開來的區間大于1
		{
			//因為我們先取尾,所以問先入頭
			StackPush(&st, start);
			//再入尾
			StackPush(&st, keyi - 1);
		}
	}
	//與初始化聯動的堆疊銷毀
	StackDestroy(&st);
}



// 歸并排序遞回子函式
void _MergeSort(int* a, int left, int right, int* tmp){
	//左大于右說明是空陣列,空陣列就跳
	//左等于右就是我們要的單體有序
	if (left >= right)
		return;
	//防溢位寫法
	int mid = left + (right - left) / 2;
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid+1,right, tmp);
	//
	int begin1 = left;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = right;
	int i = left;
	//跑空一組就直接跳
	while (begin1<=end1 && begin2<=end2){
		if (a[begin1] < a[begin2]) {
			tmp[i++] = a[begin1++];
		}			
		else {
			tmp[i++] = a[begin2++];
		}
	}
	while (begin1 <= end1) {
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2) {
		tmp[i++] = a[begin2++];
	}
	//把tmp陣列拷貝回到原來的陣列中
	i = left;
	while (i<=right)
	{
		a[i] = tmp[i];
		i++;
	}
}

// 歸并排序遞回實作
void MergeSort(int* a, int n) {
	assert(a);
	//首先創建一個臨時陣列
	int* tmp = (int*)malloc(sizeof(int) * n);
	//空就直接錯
	assert(tmp);
	//子函式
	_MergeSort(a, 0, n - 1, tmp);
	//不用了就free掉
	free(tmp);
	//然后置空
	tmp = NULL;
}
// 歸并排序非遞回實作
void MergeSortNonR(int* a, int n) {
	assert(a);
	//首先創建一個臨時陣列
	int* tmp = (int*)malloc(sizeof(int) * n);
	//空就直接錯
	assert(tmp);
	int gap = 1;
	int i = 0;
	while (gap<n){
		for (i = 0; i < n; i += 2 * gap){
			//單組需要排序的區間
			//[i,i+gap-1]  [i+gap,i+2*gap-1]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i+gap, end2 = i + 2*gap - 1;
			適用任何元素個數的核心部分
			end1出界,[begin2,end2]不存在
			//if (end1 >= n) {
			//	end1 = n - 1;
			//}
			[begin2,end2]不存在
			//if (begin2 >= n) {
			//	begin2 = n ;
			//	end2 = n - 1;
			//}
			end2出界
			//if (end2 >= n) {
			//	end2 = n - 1;
			//}
			//適用任何元素個數的核心部分
			//end1出界,[begin2,end2]不存在 都不需要歸并
			if (end1 >= n || begin2 >= n) {
				//直接跳,因為是在原陣列操作的不需要擔心最后一個沒考進去
				break;
			}
			//end2出界  需要歸并  就修正
			if (end2 >= n) {
				end2 = n - 1;
			}
			//printf("[%d,%d],[%d,%d]",begin1,end1,begin2,end2);
			重復拷貝基本是我們修正到同一個位置的原因
			我們條件斷點一下
			//if (begin1 == end1 && end1 == begin2 && begin2 == end2 && end2 == n-1)
			//{
			//	//隨便一個代碼來承接斷點,一句費代碼
			//	int a = 0;
			//}
			
			//tmp需要一個索引
			int index = i;
			while (begin1 <= end1 && begin2 <= end2){
				if (a[begin1] > a[begin2]) {
					tmp[index++] = a[begin2++];
				}
				else{
					tmp[index++] = a[begin1++];
				}
			}
			
			//肯定還有一個沒跑完
			while (begin1 <= end1){
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2) {
				tmp[index++] = a[begin2++];
			}		
			//歸一部分拷貝一部分
			int j = 0;
			for (j = i; j <= end2; j++) {
				a[j] = tmp[j];
			}
			//printf("       %d", index);
		}
		//printf("\n");
		
		一行歸并完了再考回去
		//for (i = 0; i < n; i++) {
		//	a[i] = tmp[i];
		//}
		gap *= 2;
	}	
	//不用了就free掉
	free(tmp);
	//然后置空
	tmp = NULL;
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Sort.h"

// 測驗排序的性能對比
void TestOP()
{
	//設定隨機起點
	srand(time(NULL));
	//將要創建的陣列大小
	const int N = 10000000;
	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);
	int* a8 = (int*)malloc(sizeof(int) * N);
	int* a9 = (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];
		a8[i] = a1[i];
		a9[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();
	//BubbleSort(a5, N);
	int end5 = clock();
	int begin6 = clock();
	QuickSort(a6, 0, N - 1);
	int end6 = clock();
	int begin7 = clock();
	QuickSortNonR(a7, 0, N - 1);
	int end7 = clock();
	int begin8 = clock();
	MergeSort(a8, N);
	int end8 = clock();
	int begin9 = clock();
	MergeSort(a9, N);
	int end9 = 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("BubbleSort:%d\n", end5 - begin5);
	printf("QuickSort:%d\n", end6 - begin6);
	printf("QuickSortNonR:%d\n", end7 - begin7);
	printf("MergeSort:%d\n", end8 - begin8);
	printf("MergeSortNonR:%d\n", end9 - begin9);
	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
	free(a7);
	free(a8);
	free(a9);
}
//測驗插入排序
void TestInsertSort() {
	int a[] = { 1,5,3,7,0,9 };
	InsertSort(a, sizeof(a) / sizeof(a[0]));	
	PrintArray(a, sizeof(a) / sizeof(a[0]));
}
//測驗希爾排序
void TestShellSort() {
	int a[] = { 9,1,2,5,7,4,8,6,3,5 };
	ShellSort(a, sizeof(a) / sizeof(a[0]));
	PrintArray(a, sizeof(a) / sizeof(a[0]));
}
//測驗選擇排序
void TestSelectSort() {
	int a[] = { 9,1,2,5,7,4,8,6,3,5 };
	SelectSort(a, sizeof(a) / sizeof(a[0]));
	PrintArray(a, sizeof(a) / sizeof(a[0]));
}
//測驗堆排序
void TestHeapSort() {
	int a[] = { 9,1,2,5,7,4,8,6,3,5 };
	HeapSort(a, sizeof(a) / sizeof(a[0]));
	PrintArray(a, sizeof(a) / sizeof(a[0]));
}
//測驗冒泡排序
void TestBubbleSort() {
	int a[] = { 9,1,2,5,7,4,8,6,3,5 };
	BubbleSort(a, sizeof(a) / sizeof(a[0]));
	PrintArray(a, sizeof(a) / sizeof(a[0]));
}
//測驗單趟排序
void TestPartSort1() {
	int a[] = { 5,5,5,5,5,5,5,5,5,5 };
	PartSort1(a,0 ,sizeof(a) / sizeof(a[0])-1);
	PrintArray(a, sizeof(a) / sizeof(a[0]));
}
//測驗快速排序
void TestQuickSort() {
	int a[] = { 9,1,2,5,7,4,8,6,3,5 };
	QuickSort(a, 0, sizeof(a) / sizeof(a[0]) - 1);
	PrintArray(a, sizeof(a) / sizeof(a[0]));
}
//測驗快速排序--非遞回
void TestQuickSortNonR() {
	int a[] = { 9,1,2,5,7,4,8,6,3,5 };
	QuickSortNonR(a, 0, sizeof(a) / sizeof(a[0]) - 1);
	PrintArray(a, sizeof(a) / sizeof(a[0]));
}
//測驗歸并排序--遞回
void TestMergeSort() {
	int a[] = { 10,6,7,1,3,9,4,2 };
	MergeSort(a, sizeof(a) / sizeof(a[0]));
	PrintArray(a, sizeof(a) / sizeof(a[0]));
}
//測驗歸并排序--非遞回
void TestMergeSortNonR() {
	int a[] = {  10,6,7,1,3,9,4,2,5 };
	MergeSortNonR(a, sizeof(a) / sizeof(a[0]));
	PrintArray(a, sizeof(a) / sizeof(a[0]));
}
int main(){
	//TestInsertSort();
	//TestShellSort();
	//TestSelectSort();
	//TestHeapSort();
	//TestBubbleSort();
	//TestPartSort1();
	//TestQuickSort();
	//TestQuickSortNonR();
	//TestMergeSort();
	//TestMergeSortNonR();
	TestOP();
	return 0;
}

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

標籤:java

上一篇:即使條件不滿足,處理庫CSV也會不斷添加行

下一篇:CGBTN2111-DAY09總結復習

標籤雲
其他(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