首頁 > 軟體

C語言非遞迴演演算法解決快速排序與歸併排序產生的棧溢位

2022-04-06 13:02:00

建議還不理解快速排序和歸併排序的小夥伴們可以先去看我上一篇部落格​​​​​​哦!C語言超詳細講解排序演演算法下篇

1、棧溢位原因和遞迴的基本認識

我們先簡單來了解下記憶體分佈結構:

棧區:用於存放地址、臨時變數等;                                                                           

堆區:程式執行期間動態分配所使用的場景;

靜態區:存放全域性變數和靜態變數,具體還分為 .bss段和.data段;

               .bss段:存放未初始化的和初始化為0的全域性變數或者靜態變數;

               .data段:初始化不為0的全域性變數或者靜態變數;

常數區:存放常數(比如比變數名字,非0的初始化值,const常數,字串等),唯讀;

程式碼區:存放程式碼的位置,唯讀;

 我們再來看這樣的一串程式碼執行的結果:

這是一個累加求和的遞迴函數,當我們發現累加求和到1000遞迴仍然能正常執行,我們接著改為10000看看是否還能正常執行? 

我們可以看到,當數值達到10000的時候程式已經崩了,並不會顯示任何錯誤,當我們進入偵錯可以發現報錯顯示棧溢位,那為什麼會造成棧溢位呢,我們接著往下看! 

 遞迴的基本認識:

 遞迴本質也是函數呼叫,是函數呼叫,本質就要形成和釋放棧幀

 呼叫函數是有成本的,這個成本就體現在形成和釋放棧幀上:時間+空間

 所以,遞迴就是不斷形成棧幀的過程

記憶體和CPU的資源是有限的,也就決定了,合理的遞迴是絕對不能無限遞迴下去,如果遞迴呼叫深度太深,這樣有可能導致一 直開闢棧空間,最終產生棧空間耗盡的情況,這樣的現象我們稱為棧溢位!

既然使用遞迴極端情況下會出現棧溢位的問題,那麼我們就用非遞迴的方式來實現快速排序和歸併排序!

2、快速排序(非遞迴實現)

快速排序非遞迴實現思想:

首先我們可以藉助資料結構的棧來完成,遵循棧的後進先出,我們可以先入右再入左,然後使用我們上一期講的三個方法中的其中一個方法,這裡我們選擇挖坑法,使用挖坑法我們可以看作成兩個區間也就是: [left, keyIndex - 1] 和 [keyIndex + 1, right],如果區間存在我們接著入棧,如此迴圈直到棧為空,則排序結束!

圖解見下:

 程式碼實現如下:

//挖坑法 - 升序
int PartSort(int* a, int left, int right)
{
	int begin = left;
	int end = right;
	int key = a[begin];
	int pivot = begin;
 
	while (begin < end)
	{
		while (begin < end && a[end] >= key)
		{
			--end;
		}
		a[pivot] = a[end];
		pivot = end;
 
		while (begin < end && a[begin] <= key)
		{
			++begin;
		}
		a[pivot] = a[begin];
		pivot = begin;
	}
 
	pivot = begin;//當begin與end相遇,隨便把begin和end的值給pivot
	a[pivot] = key;
 
	return pivot;
}
 
void QuickSortNonR(int* a, int n)
{
	Stack st;
	StackInit(&st);//初始化棧
	StackPush(&st, n - 1);//入陣列最後一個元素下標
	StackPush(&st, 0);//入陣列第一個元素下標
 
	while (!StackEmpty(&st))//當棧不為空我們就進入迴圈
	{
		int left = StackTop(&st);//取出棧頂元素給left
		StackPop(&st);//出棧 - 刪除棧頂元素
 
		int right = StackTop(&st);
		StackPop(&st);
 
		int keyIndex =  PartSort(a, left, right);//使用挖坑法區間排序
 
		//[left, keyIndex - 1] keyIndex [keyIndex + 1, right]  -  分成子區間
 
		if (keyIndex + 1 < right)//因棧後進先出的特性,所以先入右區間
		{
			StackPush(&st, right);
			StackPush(&st, keyIndex + 1);
		}
 
		if (left < keyIndex - 1)
		{
			StackPush(&st, keyIndex - 1);
			StackPush(&st, left);
		}
	}
 
	StackDestory(&st);//銷燬棧
}

3、歸併排序(非遞迴實現)

歸併排序非遞迴實現思想:

上期我們知道歸併需要開闢一個陣列,並且使用分治的演演算法來實現歸併排序,而非遞迴版本我們的思路也是差不多,先讓他們一個一個歸併,然後兩個兩個歸併,再接著四個四個一起歸併,具體圖解見下:

 程式碼實現如下:

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc:");
		return;
	}
 
	int gap = 1;//gap為每組資料的個數,每次翻倍
 
	while (gap < n)
	{
		for (int 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;
 
			//歸併過程中右半區間有可能不存在!
			if (begin2 >= n)
				break;
			//歸併過程中右半區間越界了,就修正下
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			
			int index = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}
 
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
 
			//拷貝進去
			for (int j = i; j <= end2; ++j)
			{
				a[j] = tmp[j];
			}
 		}
		gap *= 2;
	}
	free(tmp);
}

本期到這裡就結束了,相信你們已經對非遞迴快速排序和歸併排序已經很瞭解了,非遞迴這兩個在校招中經常會考,加油把!

gitee(碼雲):Mercury. (zzwlwp) - Gitee.com       

到此這篇關於C語言非遞迴演演算法解決快速排序與歸併排序產生的棧溢位的文章就介紹到這了,更多相關C語言 棧溢位內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com