三種線性排序算法
- 基數排序、計數排序和桶排序都是特定情況下時間複雜度爲O(N) 的算法(對於特定算法可能需要加上常數項),他們都屬於非比較的排序算法。但是他們的空間複雜度都不是O(1) (部分基於比較的排序算法可以做到O(1) 的空間複雜度)。
基數排序
- 基於比較的排序方法無法做到在O(N) 的時間複雜度內完成對數據的排序,但是基數排序不是基於比較的排序方法。
- 基於排序的原理是將整數按位數切割成不同的數字,然後按每個位數分別比較。由於整數也可以表達字符串(比如名字或日期)和特定格式的浮點數,所以基數排序也不是隻能使用於整數。
- 基數排序的平均時間複雜度爲O(dN) ,d是數據的位數,空間複雜度是O(kN) ,k爲一個基數所具有的可能的取值個數(10進制的話,就是k=10)
- 參考鏈接
- 注意:雖然基數排序的時間複雜度很小,但是一方面他的空間複雜度很大(基於比較的排序一般空間複雜度都是O(1) ),另一方面很多需要排序的數據沒有基數,因此無法使用基數排序。
代碼
#include <cstdlib>
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#include <unordered_map>
#include <algorithm>
#include <map>
#include <set>
#include <stdio.h>
#include <string.h>
using namespace std;
typedef long long ll;
int dirs[8][2] = { 1, 1, 1, 0, 1, -1, 0, 1, 0, -1, -1, 1, -1, 0, -1, -1 };
// 獲取最大位數
int maxBit(const vector<int>& data)
{
int d = 1;
int p = 10;
int ret = 0;
for (int i = 0; i < data.size(); ++i)
{
d = 1;
p = 10;
while ( data[i] >= p )
{
p *= 10;
++d;
}
ret = max( ret, d );
}
return ret;
}
// 獲取數字num在第pos位的數值
int getDigit( const int& num, int pos )
{
int p = 1;
for (int i = 1; i < pos; i++)
{
p *= 10;
}
return (num / p) % 10;
}
// 從低位開始的基數排序
void LSDRadixSort(vector<int>& nums)
{
if (nums.size() <= 1)
return;
int bits = maxBit( nums );
vector<int> bucket(nums.size(), 0);
vector<int> count(10, 0);
for (int k = 1; k <= bits; ++k)
{
for (int i = 0; i < 10; ++i)
count[i] = 0;
for (int i = 0; i < nums.size(); ++i)
++count[getDigit(nums[i], k)];
// count[i]表示第i個桶的右邊界index
for (int i = 1; i < 10; ++i)
{
count[i] += count[i - 1];
}
// 從右向左掃描,保證排序的穩定性
// 當前位不同時,能夠保證後面的數比前面的數大
for (int i = nums.size() - 1; i >= 0; --i)
{
int j = getDigit( nums[i], k );
// 將nums[i]從到左依次放置
bucket[count[j] - 1] = nums[i];
// 右邊界向左減一
--count[j];
}
// 保存當前的排序順序
// 注意:這一步保證第k-1位的都已經被正確排序,並且之後該順序不會被打亂
for (int i = 0; i < nums.size(); ++i)
nums[i] = bucket[i];
}
}
// 從高位開始的基數排序的遞歸調用,
// 加入了begin和end,方便對區域子桶中的數據進行進一步的排序
void mergeMSD(vector<int> &nums, int begin, int end, int bits)
{
vector<int> count(10, 0);
vector<int> bucket( end-begin, 0 );
for (int i = begin; i < end; ++i)
++count[getDigit(nums[i], bits)];
for (int i = 1; i < 10; i++)
{
count[i] += count[i - 1];
}
for (int i = end - 1; i >= begin; --i)
{
int j = getDigit( nums[i], bits );
bucket[count[j] - 1] = nums[i];
--count[j];
}
for (int i = begin; i < end; ++i)
{
nums[i] = bucket[i];
}
// 遞歸對每個子桶中的數據進行排序
for (int i = 0; i < 9; i++)
{
int p1 = begin + count[i];
int p2 = begin + count[i+1];
if (p1 + 1 < p2 && bits > 1)
mergeMSD( nums, p1, p2, bits-1 );
}
}
// 從高位開始的基數排序
void MSDRadixSort(vector<int> &nums)
{
if (nums.size() <= 1)
return;
int bits = maxBit(nums);
mergeMSD(nums, 0, nums.size(), bits);
}
int main()
{
int row = 0, col = 0;
// ifstream is("data.txt", ios::in);
vector<int> nums = {23, 18, 98, 67, 101};
LSDRadixSort( nums );
for_each(nums.begin(), nums.end(), [](int num) { cout << num << " "; });
cout << endl;
nums = { 23, 18, 98, 67, 101 };
MSDRadixSort(nums);
for_each(nums.begin(), nums.end(), [](int num) { cout << num << " "; });
cout << endl;
system("pause");
return 0;
}
計數排序
- 對於一些數據非常集中的整數,可以使用這種方法進行排序
代碼
#include <cstdlib>
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#include <unordered_map>
#include <algorithm>
#include <map>
#include <set>
#include <stdio.h>
#include <string.h>
using namespace std;
typedef long long ll;
void countSort( vector<int> &nums )
{
if (nums.size() <= 1)
return;
int maxV = nums[0];
int minV = nums[0];
for (int i = 1; i < nums.size(); ++i)
{
maxV = max( maxV, nums[i] );
minV = min(minV, nums[i]);
}
// 建立help數組,存儲每個值(減去最小值之後)出現的次數
vector<int> help( maxV-minV+1, 0 );
for (int i = 0; i < nums.size(); ++i)
++help[ nums[i] - minV ];
int index = 0;
for (int i = 0; i < help.size(); ++i)
{
while (help[i] > 0)
{
nums[index++] = i + minV;
--help[i];
}
}
}
int main()
{
int row = 0, col = 0;
// ifstream is("data.txt", ios::in);
vector<int> nums = {6,6,4,3,5,7,9,3,20};
countSort( nums );
for_each(nums.begin(), nums.end(), [](int num) { cout << num << " "; });
cout << endl;
system("pause");
return 0;
}
桶排序
- 數據數據偏差很大的情況(最大值與最小值偏差很大),此時使用計數排序十分浪費空間,我們可以使用桶排序。
- 建立K個桶,每個桶中放置一個子數組,保證靠前的數組中的最大值小於靠後的數組中的最小值,然後對桶內數據進行排序,最後再合併即可。
- 使用桶排序時,我們假定數據是均勻隨機分佈的,否則如果數據被分到同一個桶內,會造成消耗大量內存空間的基礎上(空間複雜度),排序的時間複雜度也不是O(N) 。
代碼
#include <cstdlib>
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#include <unordered_map>
#include <algorithm>
#include <map>
#include <set>
#include <stdio.h>
#include <string.h>
using namespace std;
typedef long long ll;
void bucketSort( vector<int> &nums )
{
if (nums.size() <= 1)
return;
int maxV = nums[0];
int minV = nums[0];
for (int i = 1; i < nums.size(); ++i)
{
maxV = max(maxV, nums[i]);
minV = min(minV, nums[i]);
}
// 桶的數量,這裏可以隨意指定,但是對於具體情況,建議具體分析
int bucketNumber = (maxV - minV) / 10 + 1;
vector<vector<int>> bucketNums(bucketNumber);
for (int i = 0; i < nums.size(); i++)
{
int num = (nums[i] - minV) / 10;
bucketNums[num].push_back( nums[i] );
}
// 對於每個桶中使用的排序算法的複雜度就是桶排序的最壞時間複雜度
for (int i = 0; i < bucketNums.size(); i++)
{
std::sort(bucketNums[i].begin(), bucketNums[i].end());
}
int index = 0;
for (int i = 0; i < bucketNums.size(); i++)
{
for (auto &num : bucketNums[i])
nums[index++] = num;
}
}
int main()
{
int row = 0, col = 0;
// ifstream is("data.txt", ios::in);
vector<int> nums = {6,6,4,3,5,7,9,3,20};
bucketSort( nums );
for_each(nums.begin(), nums.end(), [](int num) { cout << num << " "; });
cout << endl;
system("pause");
return 0;
}