1. 程式人生 > >排序總結,插入排序 選擇排序 交換排序 歸併排序 計數排序 時間複雜度空間複雜度穩定性詳解

排序總結,插入排序 選擇排序 交換排序 歸併排序 計數排序 時間複雜度空間複雜度穩定性詳解

排序大體分為兩類:比較排序和非比較排序一    各個排序的基本實現1.直接插入排序和希爾排序
//整體思路:往一段有序區間插入元素,end標記為有序區間的最後一個元素下標,要插入的元素下標為end+1此處就稱tmp,先把他儲存起來,(否則可能被覆蓋)如果end+1這個元素
//比有序區間的小,就把有序區間元素依次向後移動,移動結束條件有兩個,1.end變為-1,2.有序區間內找到比tmp小的數。
void PrintArray ( int *a, size_t n )
{
	for ( size_t i = 0; i < n ; i++ )
	{
		cout << a[i] << " ";
	}
	cout << endl;
}
//時間複雜度:o(N^2)  最壞情況,每次插入要把前面的全移動 1+2+....n-1等差數列求和(n-1)*n/2也就是o(N^2)逆序時最壞
//            o(N)    順序最好,只比較了一遍沒進去
void InsertSort ( int *a, size_t n )
{
	assert ( a );
	
	for ( size_t i = 0; i < n-1; i++ )
	{
		int end = i;
        //單趟邏輯
		int tmp = a[end + 1];
		while (end>=0&& a[end] >tmp )
		{
			a[end + 1] = a[end];
			--end;
		}
		a[end + 1] = tmp;
	}
}
//整體思路1.預排序(使大的數很快移到後面去)分組在每組內移動接近有序 gap越大越不接近有序 2.插入排序
//1.gap>1   預排序
//2.gap==1  插入排序
void ShellSort ( int*a, size_t n )//是針對插入排序逆序的情況下,移動次數太多而設計。  希爾排序用於資料量較大
{
	assert ( a );
	int gap=n;
	//預排序:排完說明分組為gap的這些元素各自有序
	while ( gap > 1 )
	{
		gap = gap / 3+1;//加1保證了最後一次為gap=1,絕對會有序
		for ( size_t i = 0; i<n - gap; i++ )//i++保證了不是一組走完,走另一組。而是一起走。
		{
			int end = i;
			//單趟排序
			int tmp = a[end + gap];
			while ( end >= 0 && a[end] > tmp )
			{
				a[end+gap] = a[end ];
				end -= gap;
			}
			a[end + gap] = tmp;
		}
	}
}

void TestInsertSort ( )
{
	int a[] = { 2, 5, 4, 9, 3, 6, 8, 7, 1 };
	//InsertSort ( a, sizeof(a) / sizeof(a[0]));
	PrintArray ( a, sizeof(a) / sizeof(a[0]) );
}
void TestShellSort ( )
{
	int a[] = { 2, 5, 4, 9, 3, 6, 8, 7, 1 };
	ShellSort ( a, sizeof(a) / sizeof(a[0]));
	PrintArray ( a, sizeof(a) / sizeof(a[0]) );
}

2.選擇排序和堆排序
//選擇排序  每次可選一個最小的數,一個最大的數
//時間複雜度  最壞o(N^2)      n-1+n-2+1  也是等差數列
//時間複雜度  最好o(N^2)     儘管你有序,可是我不知道還是要每次遍歷一遍 
void SelectSort ( int *a, size_t n )
{
	
	int end = n-1;
	int begin= 0;
	while ( begin< end )
	{
		int min = begin;
		int max = begin;
		for ( size_t i = begin; i <= end; i++ )
		{
			if ( a[i]>a[max] )
			{
				max = i;
			}
			if ( a[i] < a[min ])
			{
				min = i;
			}
		}
	/*	swap ( a[min], a[begin] );
		if (begin== max )
		{
			max = min;
		}
		swap ( a[max], a[end] );*/
		swap ( a[max], a[end] );
		if ( min == end )
		{
			min = max;
		}
		swap ( a[min], a[begin] );

		begin++;
		end--;
	}
	
}
//堆排序 升序 建大堆,把最大的數選出來,換到後面去,然後把剩下的數向下調整看成一個堆 
//選第一個數要建堆N*lg N  其他lgn 即N*lgN+(N-1)lgN=o(NlgN)
void AdjustDown ( int *a, size_t n, int root )
{
	int parent = root;  
	int child = 2 * parent;
	
	 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;
		}
		else
		{
			break;
		}
	}

}
void HeapSort ( int *a, size_t n )
{
	for ( int i = (n - 2) / 2; i >= 0; i-- )//建堆NlgN
	{
		AdjustDown ( a, n, i );//lgN
	}
	//(N-1)lgN
	int end = n - 1;
	while ( end > 0 )
	{
		swap ( a[0], a[end] );
		AdjustDown ( a, end, 0 );
		--end;
	}

}
void TestHeapSort ( )
{
	int a[] = { 2, 5, 4, 9, 3, 6, 8, 7, 1, 0 };
	HeapSort ( a, sizeof(a) / sizeof(a[0]) );
	PrintArray ( a, sizeof(a) / sizeof(a[0]) );
}
void TestSelectSort ( )
{
	int a[] = { 2, 5, 4, 9, 3, 6, 8, 7, 1 ,0};
	SelectSort ( a, sizeof(a) / sizeof(a[0]));
	PrintArray ( a, sizeof(a) / sizeof(a[0]) );
}
int main ( )
{
	//TestSelectSort ( );
	TestHeapSort ( );
	system ( "pause" );
	return 0;
}
3.氣泡排序和快速排序氣泡排序
//交換排序
//時間複雜度0(N^2) n-1+n-2+.....1  也是等差數列
//最好的情況下:0(N) 有序   
//冒泡和插入的區別:插入比冒泡好,冒泡要求更嚴格的有序 
//比如:123465  如果插入排序  是比較N-1次,插入一次 N     氣泡排序:第一趟比較 N-1次之後,已經有序可是不知道,又要來一遍 N-1
//因此插入比冒泡好,冒泡要求更嚴格有序

void BubbleSort ( int *a, size_t n )
{
	//int end = n - 1;
	//while ( end > 0 )
	//{
	//	bool ExChange = 0;
	//	for ( size_t i = 0; i < end; i++ )//單趟
	//	{
	//		if ( a[i]>a[i + 1] )
	//		{
	//			swap ( a[i], a[i + 1] );
	//			ExChange = 1;
	//		}
	//	}
	//	if ( ExChange == 0 )
	//	{
	//		break;
	//	}
	//	end--;
	//}
	for ( size_t end = n - 1; end > 0; end-- )
	{
		bool ExChange = 0;
		for ( size_t i = 0; i < end; i++ )//單趟
		{
			if ( a[i]>a[i + 1] )
			{
				swap ( a[i], a[i + 1] );
				ExChange = 1;
			}
		}
		if ( ExChange == 0 )
		{
			break;
		}
	}
}
void TestBubbleSort ( )
{
	int a[] = { 2, 5, 4, 9, 3, 6, 8, 7, 1, 0 };
	BubbleSort ( a, sizeof(a) / sizeof(a[0]) );
	PrintArray ( a, sizeof(a) / sizeof(a[0]) );
}
快速排序:
//時間複雜度:遞迴的次數乘以每次遞迴  遞迴的次數N每次遞迴lgN   因此時間複雜度為o(NlgN)
//最壞情況:0(N^2)  有序   三數取中法,解決了有序的這種情況
int GetMidindex ( int *a, int begin, int end )
{
	int mid = begin + ((end - begin) >> 1);
	if ( a[mid] > a[begin] )
	{
		if ( a[begin] > a[end] )
		{
			return begin;
		}
		else if ( a[mid] > a[end] )
		{
			return end;
		}
		else
		{
			return mid;
		}
	}
	else
	{
		if ( a[begin] > a[end] )
		{
			return begin;
		}
		else if ( a[end] > a[mid] )
		{
			return mid;
		}
		else
		{
			return end;
		}
	}
}
//左右指標法
int PartSort2 ( int *a, int begin, int end )
{
	//int& key= a[end];//為什莫給a[end]而不是和講的一樣是a[end-1],注意考慮有序情況:如果給a[end-1]反而錯了,如果給a[end]就自己和自己交換
	int mid = GetMidindex ( a, begin, end );
	swap ( a[mid], a[end] );
	int keyIndex = end;
	int key = a[end];
	while ( begin < end )
	{
		while ( begin<end&&a[begin] <= key )
		{
			begin++;
		}
		while (begin<end&& a[end]>=key )
		{
			end--;
		}
		swap ( a[end], a[begin] );
	}
	swap(a[keyIndex], a[begin]);
	return begin;
}
//挖坑法
int PartSort1 ( int*a, int begin, int end )
{
	int key = a[end];
	while ( begin < end )
	{
		while (begin<end&& a[begin] <=key )
		{
			begin++;
		}
		a[end] = a[begin];
		while (begin<end&& a[end] >= key )
		{
			end--;
		}
		a[begin] = a[end];
	}
	a[begin] = key;
	return begin;
}
//前後指標法
int PartSort3 ( int *a, int begin, int end )
{
	int& key = a[end];
	//int key=a[end]
	int cur = begin;
	int prev = begin - 1;
	while ( cur < end )
	{
		if ( a[cur] < key && (++prev) != cur )
		{
			swap ( a[prev], a[cur] );
		}
		cur++;
	}
	swap ( a[++prev], key );//swap(a[++prev],a[end]);
	return prev;

}
void QuicksortNonR ( int *a, int left, int right )
{
	stack<int >st;
	st.push ( right );
	st.push ( left );
	while ( !st.empty ( ) )
	{
		int begin = st.top ( );
		st.pop ( );
		int end = st.top ( );
		st.pop ( );
		int div = PartSort3 ( a, begin, end );
		
		if ( begin < div - 1 )
		{
			st.push ( div - 1 );
			st.push ( begin );
		}
		if ( div + 1 < end )
		{
			st.push ( end );
			st.push ( div + 1 );
		}
	}


}
void Quicksort ( int* a, int left,int right )
{
	if ( left >= right )
	{
		return;
	}
	//小區間優化
	if ( right - left < 8)//省去最後3層
	{
		InsertSort ( a+left, (right - left) + 1 );
		return;
	}
	int div = PartSort3 ( a, left, right );
	Quicksort ( a, left, div - 1 );
	Quicksort ( a, div + 1, right );

}

//有序有兩種情況:1.區間只剩一個值,說明有序
//                2.左邊有序,右邊有序
void TestQuickSort ( )
{
	int a[] = { 5, 10, 4, 9, 20, 6, 8, 7, 1, 5 };
	QuicksortNonR ( a, 0, sizeof(a) / sizeof(a[0]) - 1 );
	PrintArray ( a, sizeof(a) / sizeof(a[0]) );
}
//int main ( )
//{
//	//TestBubbleSort ( );
//
//	TestQuickSort ( );
//	system ( "pause" );
//	return 0;
//}
4.歸併排序
歸併排序
時間複雜度:0(NlgN)       空間複雜度:0(N)

void _MergeSort ( int *a, int left, int right, int* tmp )//tmp為什莫再外面開tmp,所有遞迴都可以用.如果在裡面開裡面每次遞迴都要開闢空間
{
	if ( left >= right )//如果只剩一個元素,或者沒有元素可以看作是有序的
	{
		return;
	}
	if ( right - left < 8 )//省去最後3層
	{
		InsertSort ( a + left, (right - left) + 1 );
		return;
	}
	int div = ((right - left) >> 1) + left;
	//讓兩段子區間有序再歸併
	//[left,div] [div+1 ,right]
	_MergeSort ( a, left, div, tmp );
	_MergeSort ( a, div + 1, right, tmp );
	int index = left;
	int begin1 = left; int end1 = div;
	int begin2 = div + 1; int end2 = right;
	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++];
	}
	//每次歸併完,再拷貝到原區間上去
	index = left;
	while ( index <= right )
	{
		a[index] = tmp[index];
			++index;
	}
 
} 
void MergeSort ( int *a,size_t n)
{

	int * tmp = new int[n];

	_MergeSort(a, 0, n - 1,tmp);
	delete[]tmp;

}
void TestMergeSort ( )
{
	int a[] = { 5, 10, 4, 9, 20, 6, 8, 7, 1, 5 };
	MergeSort( a,  sizeof(a) / sizeof(a[0]));
	PrintArray ( a, sizeof(a) / sizeof(a[0]) );
}
int main ( )
{

	TestMergeSort ( );
	system ( "pause" );
	return 0;
}
5.計數排序
//非比較排序
//基數排序:只能用於排整型   不多講
//計數排序:
//直接定址法的雜湊
//時間複雜度 0(max(n,range))   資料範圍比較集中的時候適合用計數排序
void CountSort ( int *a, int n )
{
	int max = a[0];
	int min = a[0];
	for ( int 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 * hashtable = new int[range];//不能開n,是相對位置
	memset ( hashtable, 0, sizeof(int)*range );
	for ( size_t i = 0; i < n; i++ )
	{
		hashtable[a[i] - min]++;//   a[i]是絕對位置,記清楚此處是相對位置
	}
	size_t j = 0;
	for ( size_t i = 0; i < range; i++ )
	{

		while ( hashtable[i]-- )
		{
			a[j] = i + min;
			++j;
		}
	}

		delete[] hashtable;
}
void TestCountSort ( )
{

	int a[] = { 5, 10, 4, 9, 20, 6, 8, 7, 1, 5 };
		CountSort( a,  sizeof(a) / sizeof(a[0]));
		PrintArray ( a, sizeof(a) / sizeof(a[0]) );
}
int main ( )
{
	TestCountSort ( );
	system ( "pause" );
	return 0;
	
}
二   時間複雜度和空間複雜度三    穩定性
穩定性:應用場景:成績排名:成績相同,先交卷子在前,後交卷子在後  具體操作:先拿時間排,再拿一個穩定的的排序對成績排,就能保證
各個排序的穩定性首先要明白所有的穩定排序都可以變成不穩定的。
插入            穩定:我能做到比你小讓你往後挪,和你相等放你後面如此便可以保證有序
希爾            不穩定:相同的值可能被分到不同的組裡面  把相對順序打亂
選擇排序     不穩定 :先選到的放到最後面取
堆排序         不穩定:父親大於等於孩子 把父親的放到後面了,孩子的放次後面   相對位置變了
冒泡            穩定 :大的往後冒泡,相等不往後冒泡
快排            不穩定:比它大的往右翻,比它小的往左翻  最後後面的那個到中間 。    // 1 9 5 7 6 4 5 8 5
歸併排序      穩定:如果相等時先拿左邊的