排序 | 冒泡排序的優化與qsort快速排序

冒泡排序

冒泡排序 Bubble_Sort,是極爲簡單的一種排序算法。雖然效率差一點,但好在具有結構簡單,容易理解,易於操作等優點。冒泡排序就是把小的元素往前調或者把大的元素往後調。在相鄰的兩個元素間比較,交換也發生在這兩個元素之間。

冒泡排序是一種穩定排序算法,在排序後相同元素的前後順序並沒有改變。

相比於傳統的冒泡排序,平均時間複雜度爲O(n2),最好的時間複雜度爲2,是一種效率不高的的排序。但勝在使用方便,於是便有了一些對於冒泡的優化算法。

這裏,我總結了以下兩種優化方案:

  1. 使用標記,在冒泡排序有序後提前退出排序
  2. 在兩個方向上來回進行進行冒泡,使待排數列每次縮短兩步

爲了不使程序太過複雜,這裏我們採用類似於波動曲線的一種方式進行方案二算法的設計。另外方案二與方案一也可以相互結合使用。三種排序算法特點如下所示:
在這裏插入圖片描述

測試三種排序效率

這裏我們直接開始測試三種排序的效率,通過以下代碼測試最終的優化排序方案,最後與標椎庫<stdlib.h>中提供的qsort() 函數進行比較。

輔助函數與主程序代碼部分
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// 交換兩整型變量
void Swap_Int(int* pa, int* pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

// 輸出arr數組中start下標~stop下標
void Show_Ar(int* arr, int indexStart, int indexStop)
{
	for (int i = indexStart; i < indexStop; ++i)
	{
		printf("%d   ", arr[i]);
	}
	printf("\n");
}

     /* 三種冒泡排序 */
void Bubble_Sort1(int *arr, int n);
void Bubble_Sort2(int* arr, int n);
void Bubble_Sort3(int* arr, int n);

// 測試各排序算法的速度
void Speed(void (*pFun)(int *, int ));

int main()
{

	Speed(Bubble_Sort1);

	Speed(Bubble_Sort2);

	Speed(Bubble_Sort3);

	return 0;
}
測試三種排序的速度方法
// 測試排序算法的速度
void Speed(void (*pFun)(int*, int))
{
	// 測試對十萬個數字的排序
	const int n = 100000;

	// 用於測試排序功能是否正常
	//int arr[n] = { 67,45,90,78,89,23,34,100,12,56 };

	int arr[n];

	// 隨機賦值
	srand((unsigned)time(NULL));
	for (int i = 0; i < n; ++i)
	{
		arr[i] = rand() % 1000;
	}

	// 定義計時器
	clock_t start, stop;

	// 由於數據過多,這裏輸出排序後的 10000——10020
	start = clock();
	(*pFun)(arr, n);
	Show_Ar(arr, 10000, 10020);
	stop = clock();

	// 輸出三種排序所用時間
	printf("排序用時 %d 秒\n", (stop - start) / CLOCKS_PER_SEC);
}

三種冒泡排序方法

// 常規的冒泡函數
void Bubble_Sort1(int* arr, int n)
{

	for (int i = 0; i < n - 1; ++i)
	{	
		
		for (int j = 0; j < n - i - 1; ++j)
		{
			if (arr[j] > arr[j + 1])
			{
				Swap_Int(&arr[j], &arr[j + 1]);
			}
		}

	}

}


// 優化——使用標記
void Bubble_Sort2(int* arr, int n)
{


	for (int i = 0; i < n - 1; ++i)
	{	
		// 優化:判斷有序
		int flg = 0;

		for (int j = 0; j < n - i - 1; ++j)
		{
			if (arr[j] > arr[j + 1])
			{
				Swap_Int(&arr[j], &arr[j + 1]);
				flg = 1;
			}
		}
		if (flg == 0) break;

	}

}


// 優化——正反雙向排序
void Bubble_Sort3(int* arr, int n)
{


	for (int i = 0; i < n - 1; ++i)
	{	// 優化:判斷有序
		int flg = 0;
		// 正着跑
		int j;
		for (j = i; j < n - i - 1; ++j)
		{
			if (arr[j] > arr[j + 1])
			{
				Swap_Int(&arr[j], &arr[j + 1]);
				flg = 1;
			}
		}
		if (flg == 0) break;

		// 反着跑
		for (int k = j - 1; k > i; --k)
		{
			if (arr[k] < arr[k - 1])
			{
				Swap_Int(&arr[k], &arr[k - 1]);
				flg = 1;
			}
		}
		if (flg == 0) break;
	}

}

測試三個冒泡排序對10萬個數據進行排序後的運行結果:

第一次測試截圖:
在這裏插入圖片描述
第二次測試截圖:
在這裏插入圖片描述
第三次測試截圖:
在這裏插入圖片描述

qsort 排序算法測試

qsort() 是C標椎庫中自帶的一種快速排序算法,排序速度相當快,並且是一種泛型的排序算法。下面是 qsort() 的用法。

// <stdlib.h>
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
base -- 指向要排序的數組的第一個元素的指針。
nitems -- 由 base 指向的數組中元素的個數。
size -- 數組中每個元素的大小,以字節爲單位。
compar -- 用來比較兩個元素的函數。

上圖爲三種排序的執行結果,接下來使用 qsort() 函數進行排序測試。這裏也使用Speed() 函數對測試排序的算法進行封裝。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>


int Cmp_Int(const void* a, const void* b)
{
	return (*(int*)a - *(int*)b);
}

// 輸出arr數組中start下標~stop下標
void Show_Ar(int* arr, int indexStart, int indexStop)
{
	for (int i = indexStart; i < indexStop; ++i)
	{
		printf("%d   ", arr[i]);
	}
	printf("\n");
}

void Speed();

int main()
{
	int n = 3;
	while (n--)
	{
		Speed();
	}

	return 0;
}

// 測試排序算法的速度
void Speed()
{
	// 測試對十萬個數字的排序
	const int n = 100000;

	int arr[n];

	// 隨機賦值
	srand((unsigned)time(NULL));
	for (int i = 0; i < n; ++i)
	{
		arr[i] = rand() % 1000;
	}

	// 定義計時器
	clock_t start, stop;

	// 由於數據過多,這裏輸出排序後的 10000——10020
	start = clock();
	qsort(arr, n, sizeof(int), Cmp_Int);
	Show_Ar(arr, 10000, 10020);
	stop = clock();

	// 輸出排序所用時間
	printf("排序用時 %d 秒\n", (stop - start) / CLOCKS_PER_SEC);
}

第一次排序結果:(由於排序速度過快,不到1秒中就完成了十萬個數據的排序)
在這裏插入圖片描述
由於我們計算機棧區的大小限制,不便於測試更大的數據。所以我們改從堆區申請一百萬數據進行排序。要知道棧由系統自動分配,速度較快,堆區由程序猿手動申請,且操作效率要慢於棧區。

修改源碼,其中///***////中內容爲修改處

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


int Cmp_Int(const void* a, const void* b)
{
	return (*(int*)a - *(int*)b);
}
////////////////////////////////////////////////////////////////////////
// 輸出arr數組中start下標~stop下標,以step步長					//增加步長參數
void Show_Ar_Step(int* arr, int indexStart, int indexStop, int step)
{
	for (int i = indexStart; i < indexStop; i+=step)
	{
		printf("%d   ", arr[i]);
	}
	printf("\n");
}
///////////////////////////////////////////////////////////////////////

void Speed();

int main()
{
	int n = 3;
	while (n--)
	{
		Speed();
	}

	return 0;
}

// 測試排序算法的速度
void Speed()
{
	// 測試對一千萬個數字的排序
	const int n = 10000000;							//////////////

//////////////////////////////////////////////////////////////////
	int* arr = (int*)malloc(sizeof(int) * n);			//堆區申請
	assert(arr != NULL);
/////////////////////////////////////////////////////////////////

	// 打印arr數組的容量
	printf("共有數據 %d 個\n", n );					////////////

	// 隨機賦值
	srand((unsigned)time(NULL));
	for (int i = 0; i < n; ++i)
	{
		arr[i] = rand() % 1000;
	}

	// 定義計時器
	clock_t start, stop;

	// 由於數據過多,這裏輸出排序後的 100000——500000,步長爲10000
	start = clock();
	qsort(arr, n, sizeof(int), Cmp_Int);
	Show_Ar_Step(arr, 100000, 300000,10000);		//////////
	stop = clock();

	// 輸出排序所用時間
	printf("排序用時 %d 秒\n", (stop - start) / CLOCKS_PER_SEC);
}

對一千萬數據進行排序後的輸出結果:
在這裏插入圖片描述
可以看到,qsort() 排序真的是非常的快,無論對冒泡排序怎樣的優化之間的差距還是非常的大。

以下,是本次測試的數據對比:
冒泡排序:  十萬數據    平均用時73秒   (😭完敗)
優化冒泡:  十萬數據    平均用時64秒  (😛有效果)
qsort排序:  千萬數據    平均用時 3秒  (😋完勝)


雖然,C標椎庫中爲我們實現了排序算法,我們也不能太過依賴標椎庫。每一種算法都有他獨特的思想在其中,在平時的練習中也要多加練習其他的排序算法。另外,qsort是基於快排優化而來,下次我們可以通過優化快排和qsort進行測試比較。

附:C語言泛型編程——泛型冒泡排序: https://blog.csdn.net/weixin_43919932/article/details/90573445

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章