前言:趁著京東618,也瘋狂的剁手了一回,狂買了幾本編程相關的書籍。從12年開始接觸編程,一直到現在,斷斷續續的也學習了4,5種語言,像什么C/C++/C#,Object-C, Java, PHP等。不過一直以來的目的主要都是為了工作,需要什么學習什么,沒有什么具體的目標,或者說沒有深入的去研究一下算法,編程思想之類的。剛好,最近的工作不是很飽和,就找一些相關的書籍充實一下自己,提高一下。目前到手的有《編程珠璣》,《代碼的整潔之道》,《計算機程序設計》等,現在就講講讀后感,或者說一些習題的代碼實現等。
第一章
主要是一些基礎性的介紹,其中涉及到的問題也就是一個排序,如何使用最短的時間,最小的空間,去完成一個大型的整形數組的排序。
具體問題如下
題目:一個最多包含n個正整數的文件,每個數都小于n,其中n=10^7,且所有正整數都不重復。求如何將這n個正整數升序排列。
約束:最多有1MB的內存空間可用,有充足的磁盤存儲空間。
方法1(多通道法):
第1遍遍歷文件,將文件中范圍在1~ 249 999的正整數讀取進入1MB內存,排序(可以使用各種排序方法),將排序后的正整數存儲在磁盤文件temp中
第2遍遍歷文件,將文件中范圍在250 000~499 999的正整數讀取進入1MB內存,排序,將排序后的正整數加入存儲在磁盤文件temp中
….
第40遍遍歷文件,將文件中范圍在10^7-250 000~10^7的正整數讀取進入1MB內存,排序,將排序后的整數加入存儲在磁盤文件temp中
輸出temp文件
方法2(位圖法):
我們想使用hash映射,將對應的正整數映射到位圖集合中。即將正整數映射到bit集合中,每一個bit代表其映射的正整數是否存在。
比如{1,2,3,5,8,13}使用下列集合表示:
0 1 1 1 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0
我們可以使用具有10^7位的字符串來表示這個文件。其中,當且僅當整數i在文件中存在時候,第i位為1
接著,我們來完成具體的方法實現,首先我們約束一些基本的全局變量
// int 類型有4個字節,即32位
#define BITSPERWORD 32
// 定義右移的位數(即除以32,表示右移5位)
#define SHIFT 5
// 16進制31 二進制的話就是 0001 1111
#define MASK 0x1F
// 整數的最大值
#define N 10000000
// 最大數量
#define K 1000000
/// 大型數組必須是全局變量,或者使用static,vector修飾,否則會直接報錯,棧的空間不夠用
// 存放輸入數據,即隨機整數數組
int a[N];
// 位圖排序使用的數組
int sortBit[1 + N/BITSPERWORD];
在完成具體的排序工作之前,我們還有一個準備工作需要完成,就是實現一個隨機整數的數組獲取。
/*
* 交換兩個數組內元素位置
*/
void swap(int *a, int *b)
{
if(*a != *b){ // 異或操作 交換兩個數字的位置
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
}
/******************************************************************************
*函數名稱:void generateDiffRandV1(int a[], int n)
*函數功能:產生互不相同的隨機數
*入口參數:
*返 回 值:無
*備 注:以空間換時間
*******************************************************************************/
void generateDiffRandV1(int a[], int n, int k)
{
int i;
time_t t;
for (i = 0; i < n; i++){
a[i] = i;
}
srand((int)time(&t));
for (i = 0; i < k; i++){
swap(&a[i], &a[i+rand()%(n-i)]);
}
}
/******************************************************************************
*函數名稱:void generateDiffRandV2(int a[], int n)
*函數功能:產生互不相同的隨機數(產生隨機數的范圍是1~n-1)
*入口參數:
*返 回 值:無
*
*思 路:先生成一個放置座號的數組,然后從中隨機抽取,抽取后為防止重復,立即歸零。
* :每次生成座號,只需判斷是否為0 即可,大大提高了程序執行的效率。
*******************************************************************************/
void generateDiffRandV2(int a[], int n)
{
int *flag =(int *)malloc(sizeof(int) * n);
static int flag_once = 0;
int i, index;
for(i = 0; i < n; i++)
flag[i] = i+1;
if(!flag_once){
srand((unsigned)time(0));
flag_once = 1;
}
for(i = 0; i < n;){
index = rand() % n;
if(flag[index] != 0){
a[i++] = flag[index]-1;
flag[index] = 0;
}
}
free(flag);
}
- 第一種情況:該數組中每一個數字最多出現一次
通過構建新的整形數組,來存放相關的位圖信息。
整形的長度為4個字節,即32位,則數組長度為N/32 + 1。而每一個整形元素都可以表示相對應的區間內,數字是否存在。比如說待排序集合
{1,2,3,5,8,13}
可以表示為:0-1-1-1 0-1-0-0 1-0-0-0 0-1-0-0
,在整形數組中,則表示為8494,0-0-1-0 0-0-0-1 0-0-1-0 1-1-1-0
。這個時候,我們就需要查找該位置對應的數組里面的位置。首先,i/32(從位圖的方面理解就是,i>>5,向右位移5位),即表示在數組中的下標。然后i%32,對32取余(位圖算法就表示i & 0x1F),獲取他在整形的二進制文件中的位置,得到結果后,設為1。
比如13,二進制位0001 0101
, 右移5位,結果為0,則該位圖信息應該存放在sortBit[0]
,同時,13對32取模,即余數為13,位圖運算為0001 0101 & 1111 1111 = 0001 0101
,即在13位我們需要設置為1,那么,將1左移到13位,即0000 0001 << 0001 0101 = 0010 0000 0000 0000
假設之前的位圖信息為0-0-0-0 0-0-0-1 0-0-1-0 1-1-1-0
,即表示1,2,3,5,8
存在,兩者取或,0-0-1-0 0-0-0-0 0-0-0-0 0-0-0-0 | 0-1-1-1 0-1-0-0 1-0-0-0 0-0-0-0 = 0-0-1-0 0-0-0-1 0-0-1-0 1-1-1-0
具體的代碼如下:
/*
* 設置對應的位置為1
*/
void set(int i) {
/// i & MASK 相當于 除以32然后取余
sortBit[i>>SHIFT] |= (1<<(i & MASK));
}
/*
* 初始化數據為0
*/
void clr(int i) {
/// i & MASK 相當于 除以32然后取余
sortBit[i>>SHIFT] &= ~(1<<(i & MASK));
}
/*
* 查找該數字是否存在
*/
int test(int i) {
/// i & MASK 相當于 除以32然后取余
return sortBit[i>>SHIFT] & (1<<(i & MASK));
}
通過C++的bitset來完成
// 導入相關類庫
#include <bitset>
void sortByBitSet(int size) {
int n = 0;
// 位數可以多1位,避免數據丟失
bitset<1 + N> bit; //初始默認所有二進制位為0
while(n < size)
{
bit.set(a[n], 1); //將第n位置1
n ++;
}
for(int i = 0; i <= size + 1; i++)
{
if(bit[i] == 1)
cout << i << " ";
}
cout << endl;
}