1. 程式人生 > >【排序五】非比較排序(計數排序&&基數排序)

【排序五】非比較排序(計數排序&&基數排序)

比較排序:

一、計數排序

1、基本思想

     給定一組要排序的序列,找出這組序列中的最大值,然後開闢一個最大值加1大小的陣列,將這個數組裡面的元素全部置零,然後用這個陣列統計出要排序的序列中各個元素出現的次數。等到統計完成的時候,排序就已經完成了。 圖解(假設升序)
2、演算法執行步驟:

1>找出待排序序列裡的最大值max;

2>開闢一個大小為max+1的臨時陣列tmp ,將陣列元素的所有初值賦為0;

3>遍歷待排序序列,將序列中出現值作為下標,對tmp 陣列的對應位置資料進行++;

4>上述操作完成後,tmp中統計了每個資料在待排序列中出現的次數;

3>最後,將tmp數組裡值不為0的所有下標拷進原序列(注意同一個下標可能有多個重複值,都要進行拷貝,不能遺漏),排序就算完成。

3、計數排序的優化

      從上面的例子中可以看出,3是待排序序列的最小值,在tmp陣列中3 之前的位置並沒有什麼卵用,那麼基於節省空間的角度進行考慮,我們可以開闢更小的臨時陣列統計次數,同樣能完成排序。

    比如要對1000,999,1008這三數進行排序,按照普通的方法前998個空間都被浪費掉了,所以這時候我們對它進行優化。找出要排序的這組元素中的最大值和最小值,這樣就確定了這組元素的範圍,然後開闢這個範圍加1大小的陣列,然後再將要排序的元素對映到這個新開闢的陣列中就可以了。

結論:計數排序適用於資料比較集中的序列排序

4、時間複雜度&&空間複雜度

    計數排序是一種非比較的排序方法,它的時間複雜度是O(N+K)空間複雜度是0(K),其中K是要排序的陣列的範圍。可以看出計數排序是一種以空間呢換取時間的方法。如果當K>N*logN的時候,計數排序就不是好的選擇了,因為基於比較排序的演算法的下限是O(N*logN)。 

5、程式碼實現

#pragma once
#include <iostream>
#include <assert.h>
using namespace std;

void PrintArray(int* a,size_t n)
{
	for (size_t i = 0; i < n; ++i)
	{
		cout<<a[i]<<" ";
	}
	cout<<endl;
}


void CountSort(int* a,size_t n)
{
	//查詢待排序序列的最大值
	int max = a[0];
	for (size_t i = 0; i < n-1; ++i)
	{
		if (max < a[i+1])
		{
			max = a[i+1];
		}
	}

	//開闢臨時陣列用來統計資料出現次數
	int* tmp = new int[max+1];
	//對臨時陣列初值賦為0
	memset(tmp,0,sizeof(int)*(max+1));
	//統計資料出現次數
	for (size_t i = 0; i < n; ++i)
	{
		tmp[a[i]]++;
	}

	//將資料考回原陣列
	int index = 0;
	for (int i = 0; i <= max; ++i)
	{
		while(tmp[i]--)
		{
			a[index] = i;
			++index;
		}
	}
	delete []tmp;
}

//計數排序的優化
void CountSort1(int* a,size_t n)
{
	//找到待排序序列的最大值和最小值
	int max = a[0];
	int min = a[0];
	for (size_t i = 0; i < n-1; ++i)
	{
		if (max < a[i+1])
		{
			max = a[i+1];
		}
		if (min > a[i+1])
		{
			min = a[i+1];
		}
	}

	//開闢適當空間的陣列
	int range = max - min + 1;
	int* tmp = new int[range];
	memset(tmp,0,sizeof(int)*(range));
	for (size_t i = 0; i < n; ++i)
	{
		tmp[a[i]-min]++;
	}

	//將資料考回原陣列
	int index = 0;
	for (int i = 0; i < range; ++i)
	{
		while(tmp[i]--)
		{
			a[index] = i+min;
			++index;
		}
	}
	delete []tmp;
}

void TestCountSort()
{
	int a[] = {2,5,4,9,3,6,8,7,1,0};
	int a1[] = {73,20,93,43,55,14,28,65,39,81};
	size_t sz = sizeof(a)/sizeof(a[0]);
	size_t sz1 = sizeof(a1)/sizeof(a1[0]);

	CountSort(a,sz);
	CountSort1(a1,sz1);
	PrintArray(a,sz);
	PrintArray(a1,sz1);
}
執行結果:



二、基數排序

1、基數排序的分類
LSD-- Least Significant Digit first(從低位向高位排)
MSD-- Most Significant Digit first(從高位向低位排)

由於原理差不多,此文只針對LSD進行分析。

2、基本思想

    基數排序又稱"桶子法",它從低位開始將待排序的數按照這一位的值放到相應的編號為0到9的桶中。等到低位排完之後得到一個序列,再將這個序列按照次低位的大小進入相應的桶中。以此類推,直到將所有的位都排完之後,這組數就已經有序了。

圖解(假設升序)


3、演算法執行步驟

1》統計待排序序列中最大的數是幾位數;

2》開闢一個與原陣列大小相同的臨時陣列tmp;

3》用一個count陣列統計原陣列中某一位(從個位向高位統計)相同的資料出現的次數;

4》用一個start陣列計算原陣列中某一位(從個位向高位計算)相同的資料出現的位置;

5》將桶中資料從小到大(由頂至底)用tmp陣列收集起來;

6》迴圈3》4》5》直到所有位都被統計並計算過,然後用tmp收集起來;

7》將tmp陣列的資料拷回原陣列,排序完成。

圖解:


4、時間複雜度&&空間複雜度

    基數排序是一種非比較排序,它的時間複雜度是O(N*digit)其中digit是這組待排序中的最大的數的數量級。基數排序的空間複雜度就是在分配元素時,使用的桶空間,所以基數排序的空間複雜度是O(N)。它是一種穩定的排序方法。


5、程式碼實現

#pragma once
#include <iostream>
#include <assert.h>
using namespace std;

int GetMaxDigit(int* a,size_t n)//得到陣列最大值的位數
{
	int digit = 1;
	int base = 10;

	for (size_t i = 0; i < n; ++i)
	{
		//每次與幾位數的最小資料(10、100、1000……)進行比較,
		//優點是相同位數的資料只統計一次
		while(a[i] >= base)
		{
			++digit;
			base *= 10;
		}
	}
	
	return digit;
}

void LSDSort(int* a,size_t n)
{
	assert(a);
	int base = 1;
	int digit = GetMaxDigit(a,n);//統計位數
	int* tmp = new int[n];//開闢臨時陣列

	while(digit--)
	{
		int count[10] = {0};
		//統計某位相同的資料出現次數
		for (size_t i = 0; i < n; ++i)
		{
			int index = a[i]/base %10;
			count[index]++;
		}

		int start[10] = {0};
		//統計個位相同的數在陣列a中出現的位置
		for (size_t i = 1; i < n; ++i)
		{
			start[i] = count[i-1] + start[i-1];
		}

		memset(tmp,0,sizeof(int)*n);//為tmp陣列初始化
		//從桶中收集資料(從小到大)
		for (size_t i = 0; i < n; ++i)
		{
			int index = a[i]/base %10;
			tmp[start[index]++] = a[i];
		}

		//將資料拷回原陣列
		memcpy(a,tmp,sizeof(int)*n);
		base *= 10;
	}
	delete []tmp;
}

void PrintArray(int* a,size_t n)
{
	for (size_t i = 0; i < n; ++i)
	{
		cout<<a[i]<<" ";
	}
	cout<<endl;
}
void TestLDSort()
{
	int a[] = {73,20,93,43,55,14,28,65,39,81};
	size_t sz = sizeof(a)/sizeof(a[0]);

	LSDSort(a,sz);
	PrintArray(a,sz);
}
執行結果: