1. 程式人生 > >【資料結構】八大排序之快速排序(遞迴和非遞迴方法)

【資料結構】八大排序之快速排序(遞迴和非遞迴方法)

上一博文我們講了氣泡排序,但是由於他的時間複雜度過高為O(n*n),於是在氣泡排序的基礎上今天要說的是快速排序。

本文講述兩個內容:

1.快速排序的三種方法。

2.快速排序的優化

一.什麼是快速排序???

     通過一趟排序將要排序的資料分割成獨立的兩部分其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列

 中心思想就是:找一個基準值,把資料分為兩部分,然後去排序,類似於二分查詢。

1.快速排序的的時間複雜度:

          平均為:O(n×log(n))

          最壞情況下為:O(n * n)

2.快速排序不是穩定的排序

二.快速排序遞迴的三種方法:

1.挖坑法

2.交換指標法(左右指標)

3.交換指標(前後指標)

(1.)遞迴:

1.)挖坑法(遞迴)

      1.在陣列中選取一個基準值,我在這裡選取的是陣列第一個資料

      2.定義兩個指標(left,right)分別指向陣列的首位和末尾(left = 0,right = sz - 1)

    3.將left看為第一個坑,然後用基準值和right指標所指的數比較如果比基準值大,則right指標向左移(減減)。反之則將right指標所指的值付給left所指的的值,letf指標向右移(加加)。

          再將right指標所指位置看為一個坑,用left指標所指的值和基準值比較,如果比基準值小,則left指標向右移(加加)。反之則將left所指的值付給right指標所指的值,right指標向左移。

           最後將point值賦給最後的坑的位置

我們以陣列int array[ ] = { 4, 7, 6, 5, 3, 2, 8, 1 }; 為例

程式碼展示:

void quickSort(int* array, int left, int right)
{
	if (left >= right)
		return;

	int Index = partsort(array, left, right);

	//分開而治
	//左半部分
	quickSort(array, left, Index - 1);

	//右半部分
	quickSort(array, Index + 1, right);
}


int partsort(int* array, int left, int right)
{
	//基準值
	int ponit = array[left];
	
	//坑的位置
	int temp = left;

	while (left <= right)
	{
		while (left <= right)
		{
			if (array[right] < ponit)
			{
				array[left] = array[right];
				++left;
				temp = right;
				break;
			}
			else
				--right;
		}
		while (left <= right)
		{
			if (array[left] > ponit)
			{
				array[right] = array[left];
				--right;
				temp = left;
				break;
			}
			else
				++left;
		}

	}
	array[temp] = ponit;
	return temp;
}

2.交換指標法(左右指標)

     1.和挖坑法一樣定義兩個指標(left和right)分別指向陣列的首部和陣列的尾部(left = 0,right = sz - 1)

     2.還是以陣列第一個數為基準值 int ponit = array[left]

  3.首先移動right指標,如果right指標所指的值比基準值小,則right指標不動,去移動left指標。否則right指標向左移動,知道移動到比基準值大的地方停下。

    4.移動left指標,如果left指標所指的值比基準值大,則left不動,然後交換左右指標所指的值,否則left指標想右移動,知道找到比基準值大的值停下,然後交換兩個指標所指的值。

      5.最後交換將ponit的值和left指標所指的值交換

我們以陣列int array[ ] = { 4, 7, 6, 5, 3, 2, 8, 1 }; 為例

程式碼展示:

//指標交換法(前後指標)--->遞迴
void quickSort(int* array, int left, int right)
{
	if (left >= right)
		return;

	int Index = partsort(array, left, right);

	//分開而治
	//左半部分
	quickSort(array, left, Index - 1);

	//右半部分
	quickSort(array, Index + 1, right);
}


int partsort(int* array, int left,int right)
{

	int Index = left;
	int ponit = array[Index];

		while (left < right)
		{
			while (left < right)
			{
				if (array[right] < ponit)
					break;
				else
					--right;
			}

			while (left < right)
			{
				if (array[left] > ponit)
					break;
				else
					++left;
			}


			int temp = array[right];
			array[right] = array[left];
			array[left] = temp;
		}

		
		int num = array[left];
		array[left] = array[Index];
		array[Index] = num;
		return  left;
}

(2.)非遞迴法

在上邊的三種方法中我們都用了遞迴的方法,當我們要用非遞迴時,要立刻想到棧,因為遞迴的原理符合棧的先進後出的規則,遞迴和棧在面試題和演算法中總是聯絡在一起的。

程式碼展示:

int partsort(int* array, int left, int right)
{
	//基準值
	int ponit = array[left];
	
	//坑的位置
	int temp = left;

	while (left <= right)
	{
		while (left <= right)
		{
			if (array[right] < ponit)
			{
				array[left] = array[right];
				++left;
				temp = right;
				break;
			}
			else
				--right;
		}
		while (left <= right)
		{
			if (array[left] > ponit)
			{
				array[right] = array[left];
				--right;
				temp = left;
				break;
			}
			else
				++left;
		}

	}
	array[temp] = ponit;
	return temp;
}

void QuickSort(int* array, int left, int right)
{
    
	stack<int> s;
	
	s.push(left);
	
	s.push(right);
	
	while (!s.empty())
	{
		int right = s.top();
		s.pop();
		int left = s.top();
		s.pop();

		//劃分左右部分的邊界線
		int Index = partsort(array, left, right);

		//左半部分
		if (Index - 1 > left)
		{
			s.push(left);
			s.push(Index - 1);
		}

		//右半部分
		if (Index + 1 < right)
		{
			s.push(Index + 1);
			s.push(right);
		}
	}

}

三.快速排序的優化

對於快速排序,根據我們上邊的程式碼我們可以看出最主要的是選取基準值,我上邊的程式碼都是以左邊的數為基準值。這樣就會產生一種情況:如果資料是正序或者逆序時,我們這樣選取的基準值顯然是不對的,這樣的其他資料都在基準值的一側,我們是無法劃分出兩個區域來分開排序的。為了解決這個問題就要用到以下兩個方法:

1.三數取中法

每次在排序之前,我們都無法知道資料是逆序或者是正序,如果每次去檢測那就太麻煩,所以我們選取三個數值(left,right,mid)如果是正序或者逆序那麼就直接選取中間的數值,反之任意選取兩邊任意一個數當作基準值。這樣就很容易將資料等分開來,大大提高了程式碼的效率。

(1.)挖坑法的三數取中法

int GetMid(int* array, int left, int right)
{
	//獲取中間位置
	int mid = left + ((right - left) >> 1);

	//假設是正序(升序)
	if (array[left] <= array[right])
	{
		//兩個if判斷到底是不是正序如果不是就返回邊上的,如果是就返回中間的
		if (array[mid] < array[left])
			return left;
		else if (array[mid] > array[right])
			return right;
		else
			return mid;
	}
	//假設是逆序
	else
	{
		//兩個if語句判斷你是否為逆序,如果不是就返回邊上的,如果是就返回中間的
		if (array[mid] > array[left])
			return left;
		else if (array[mid] < array[right])
			return right;
		else
			return mid;
	}

}

//挖坑法
void quickSort(int* array, int startIndex, int endIndex)
{
	if (startIndex >= endIndex)
		return;
	
	//邊界
	int Index = partsort(array, startIndex, endIndex);
	
	//分開而治
	//左半部分
	quickSort(array, startIndex, Index - 1);

	//右半部分
	quickSort(array, Index + 1, endIndex);
}

int partsort(int* array, int left, int right)
{
	int Index = left;   //坑的位置
	int num = GetMid(array, left, right);
	int temp = array[num];
	array[num] = array[left];
	array[left] = temp;
	int ponit = array[left];  //基準資料
	while (left <= right)
	{
		while (left <= right)
		{
			if (array[right] < ponit)
			{
				array[left] = array[right];
				++left;
				Index = right;
				break;
			}
			else
				--right;
		}

		while (left <= right)
		{
			if (array[left] > ponit)
			{
				array[right] = array[left];
				--right;
				Index = left;
				break;
			}
			else
				++left;
		}

	}

	array[Index] = ponit;

	return Index;
}

(2.)指標替換法

int GetMid(int* array, int left, int right)
{
	//獲取中間位置
	int mid = left + ((right - left) >> 1);

	//假設是正序(升序)
	if (array[left] <= array[right])
	{
		//兩個if判斷到底是不是正序如果不是就返回邊上的,如果是就返回中間的
		if (array[mid] < array[left])
			return left;
		else if (array[mid] > array[right])
			return right;
		else
			return mid;
	}
	//假設是逆序
	else
	{
		//兩個if語句判斷你是否為逆序,如果不是就返回邊上的,如果是就返回中間的
		if (array[mid] > array[left])
			return left;
		else if (array[mid] < array[right])
			return right;
		else
			return mid;
	}

}


void quickSort(int* array, int startIndex, int endIndex)
{
	if (startIndex >= endIndex)
		return;
	
	//邊界
	int Index = partsort(array, startIndex, endIndex);
	
	//分開而治
	//左半部分
	quickSort(array, startIndex, Index - 1);

	//右半部分
	quickSort(array, Index + 1, endIndex);
}

int partsort(int* array, int left,int right)
{

	int Index = left;
	int arr = GetMid(array, left, right);
	int temp = array[arr];
	array[arr] = array[left];
	array[left] = temp;
	int ponit = array[Index];

	while (left < right)
	{
		while (left < right)
		{
			if (array[right] < ponit)
				break;
			else
				--right;
		}

		while (left < right)
		{
			if (array[left] > ponit)
				break;
			else
				++left;
		}


		int temp = array[right];
		array[right] = array[left];
		array[left] = temp;
	}


	int num = array[left];
	array[left] = array[Index];
	array[Index] = num;
	return  left;
}

2.直接插入法(這個程式碼在以後的排序中會展示)

由於是遞迴程式,每一次遞迴都要開闢棧幀,當遞迴到序列裡的值不是很多時,我們可以採用直接插入排序來完成,從而避免這些棧幀的消耗

以上就是我自己總結的快速排序,如果有什麼錯誤,歡迎大家指出,感激不盡!!!!或者有更好的方法,希望大家能評論告訴我,跪拜!!!!希望一起進步!!!