C++資料結構和演算法:位運算、字串
--------------------------------位運算---------------------------------
Q1. 用位運算交換兩個值
前提:要交換的兩個值是獨立記憶體
void Swap(int& a, int& b) { a = a ^ b; b = a ^ b; a = a ^ b; }
Q2. 有一個數組,有些數字出現次數為偶數,只有一個數字出現次數為奇數,請在O(N)內找到這個數
解析:假設陣列為 { 2,1,3,1,2,1,3 } ,∵ N^N=0,N^0=N,∴ 0^2^1^3^1^2^1^3 = 2^1^3^1^2^1^3 = 0^1^3^1^1^3 = 0^1^1^1^0 = 0^1^1^1 = 0^0^1 = 0^1 = 1
偶數次的一定會累計異或為0,奇數次的一定會累計異或為本身,所以只要用0去累計異或所有數就行了,最後得到的就是這個奇數次數。
1 void PrintOddTimesNum(int arr[],int len) 2 { 3 int eor = 0; 4 for (int i=0;i<len;i++) 5 { 6 eor ^= arr[i]; 7 } 8 cout << eor << endl; 9 }
Q4. 上一題條件改為出現奇數次的數字有兩個,要找出這兩個數
解析:如果按上一題方法,0跟所有數異或之後得到的是 a^b,那麼怎麼分離a和b呢?
∵ 異或運算偶數次的數會抵消為0,奇數次的數會抵消為本身
∴ 所有數累計異或,一定會得到奇數次的數之間異或,eor=a^b
∵ a≠b,異或運算相同位為0,不同位為1
∴ eor至少有一位為1,而且這一位,在a中為1,在b中為0,那麼就可以把陣列分為,這1位為1的數和這一位為0的數。
假設我們先找這一位為1的數,全部累計異或,偶數次的數會抵消為0,奇數次的數會抵消為本身,所以最後會得到這一位為1且奇數次的數;
先找這一位為0的數道理一樣。
假設找到的這個數是a ,∵ eor = a^b
∴ eor^a = a^b^a = 0^b = b,用a異或eor就能得到b。
1 void PrintOddTimesNum(intarr[],int len) 2 { 3 int eor = 0; 4 for (int i=0;i<len;i++) 5 { 6 eor ^= arr[i]; 7 } //EOR = a^b 8 9 //找最右1 10 //一個數&自己的補碼得到最右1 11 //補碼 = 取反進1 12 int rightOne = eor & (~eor + 1); 13 14 int a=0; 15 for (int i=0;i<len;i++) 16 { 17 if ((arr[i] & rightOne) == rightOne) //也可以是==0來判斷 18 a ^= arr[i]; 19 } 20 21 cout << "a=" << a << " b=" << (a ^ eor) << endl; 22 }
--------------------------------字串---------------------------------
—— KMP 字串匹配演算法 ——
核心:利用匹配失敗後的資訊,儘量減少模式串與主串的匹配次數以達到快速匹配的目的。
時間複雜度:O(m+n)
第一步:構造next陣列
法則:值=公共前後綴的長度
eg. abaa
下標 | 比較元素 | 字首 | 字尾 | 值 |
0 | a | {} | {} | 0 |
1 | ab | a | b | 0 |
2 | aba | a、ab | a、ba | 1 |
3 | abaa | a、ab、aba | a、aa、baa | 1 |
第二步:匹配
比較到字尾不匹配位置,把模式串字首移動到原來的字尾位置;
1 //計算next陣列 2 int arr_next[1000]; 3 void BuildNext(const string pattern) 4 { 5 arr_next[0] = 0; // 第一個字元的最長相同字首字尾為0 6 for (int i = 1, j = 0; i < pattern.length(); i++) 7 { 8 while (j && pattern[i] != pattern[j]) 9 { 10 j = arr_next[j - 1];//如果不相同,移動p,這裡如果j=0並且兩個字元還不相同,也就預設pmt[i] = 0了 11 } 12 13 if (pattern[i] == pattern[j]) 14 { 15 j++; 16 arr_next[i] = j;//如果相同,則得到該位置pmt[i]的值,繼續向後比較 17 } 18 } 19 for (int i = 0; i < pattern.length(); i++) 20 cout << arr_next[i] << " "; 21 cout << endl; 22 } 23 24 int KMP(const string str, const string pattern) 25 { 26 for (int i = 0, j = 0; i < str.length(); i++) 27 { 28 while (j && str[i] != pattern[j]) j = arr_next[j - 1]; 29 if (str[i] == pattern[j]) 30 { 31 j++; // 兩者相等,繼續匹配 32 } 33 if (j == pattern.length()) 34 { 35 return i - j + 1;//匹配成功,返回下標 36 37 } 38 } 39 return -1;// 未匹配成功,返回-1 40 }
—— 鍵索引計數法 字串排序演算法 ——
假設要給不同班級的學生按班級排序
1 struct student 2 { 3 int stuClass; 4 string stuName; 5 }; 6 vector<student> students = { {2,"Anderson"},{3,"Brown"},{3,"Davis"},{4,"Garcia"},{1,"Harris"},{3,"Jackson"},{4,"Johnson"},{3,"Jones"},{1,"Martin"},{2,"Martinez"},{2,"Miller"},{1,"Moore"},{2,"Robinson"},{4,"Smith"}, 7 {3,"Taylor"},{4,"Thomas"},{4,"Thompson"},{2,"White"},{3,"Williams"},{4,"Wilson"} };
準備一個用於接收排序後資料的容器
vector<student> aux = students;
排序規模N,索引陣列大小R
const int N = students.size();
const int R = 5; //班級數量+1
索引陣列
int count[R+1] = { 0 }; //+1防止溢位,用於儲存計算結果
===> 計算班級出現的頻率
for (int i = 0; i < N; i++)
count[students[i].stuClass]++;
===> 將頻率轉化為索引 count[i] 表示 ≤ i 的總數
0 | 1 | 2 | 3 | 4 |
0 | 3 | 5 | 6 | 6 |
3 | 5 | 6 | 6 | |
8 | 6 | 6 | ||
14 | 6 | |||
20 |
for (int r = 0; r < R; r++)
count[r + 1] += count[r];
===> 將學生按班級分類
拆解
aux[count[students[i].stuClass - 1]++].stuClass = students[i].stuClass;
students[i].stuClass 代表 第 i 個學生所在班級
count[students[i].stuClass] 代表 小於等於 所在班級的人數
count[students[i].stuClass - 1] 代表 小於所在班級的人數
aux[count[students[i].stuClass - 1]++] 把當前學生放在 小於所在班級的人數 後面一個,再++ 表示小於所在班級的人數+1,這樣下次遇到同一班級就在這個學生後面1位了
for (int i = 0; i < N; i++)
{
aux[count[students[i].stuClass - 1]].stuName = students[i].stuName;
aux[count[students[i].stuClass - 1]++].stuClass = students[i].stuClass;
}
===> 輸出排序後結果
for (int i = 0; i < N; i++)
{
cout << "class: "<<aux[i].stuClass<<" name: "<<aux[i].stuName<< endl;
}
舉一反三:替換比較物件,可以實現按指定位排序