經典算法回顧

1、排序  refphp

<1> quicksort及partition相關問題html

1. quicksortios

code1:[首選]git

 1 int partition(vector<int> &arr, int low, int high)
 2 {
 3     int pivot = arr[low];
 4     int i = low;
 5     for (int j = low + 1; j <= high; j++)
 6     {
 7         if (arr[j] <= pivot)
 8         {
 9             i++;
10             swap(arr[i],arr[j]);
11         }
12     }
13     swap(arr[i],arr[low]);
14     return i;
15 }
16 void quicksort(vector<int>& arr, int low, int high)
17 {
18     if (low < high)
19     {
20         int r = partition(arr,low,high);
21         quicksort(arr,low,r-1);
22         quicksort(arr,r+1,high);
23     }
24 }
View Code

i總指向當前遍歷過的元素當中最後一個<=pivot的元素;j就不斷向前探尋,當發現有<=pivot的元素時,就讓i前移一位,讓i和j所指元素互換。github

ref:算法導論,編程珠璣算法

code2: [不夠簡潔]編程

 1 int partition(int s[], int l, int r)
 2 {
 3     int i = l, j = r;
 4     int x = s[l];
 5     while (i < j)
 6     {
 7         while (s[j] >= x&&i < j)
 8             j--;
 9         if (i < j)
10         {
11             s[i] = s[j];
12             i++;
13         }
14         while (s[i] <= x&&i < j)
15             i++;
16         if (i < j)
17         {
18             s[j] = s[i];
19             j--;
20         }
21     }
22     s[i] = x;
23     return i;
24 }
25 void quicksort(int s[], int l, int r)
26 {
27     if (l < r)
28     {
29         int pos = partition(s, l , r);
30         quicksort(s, l, pos - 1);
31         quicksort(s, pos + 1, r);
32     }
33 }
View Code

須要注意的:windows

每一步都要判斷i<j是否成立。數組

用l和r標識出左右區間。每一次調用l和r都不同。app

ref: http://blog.csdn.net/morewindows/article/details/6684558

迭代版本:

 1 C++(迭代版本)
 2 
 3 //參考:http://www.dutor.net/index.php/2011/04/recursive-iterative-quick-sort/
 4 struct Range {
 5     explicit Range(int s = 0, int e = 0) : start(s), end(e) {}
 6     int start, end;
 7 };
 8 void quicksort(int n, int arr[]) {
 9     if (n <= 0) return;
10     stack<Range> st;
11     st.push(Range(0, n - 1));
12     while (!st.empty()) {
13         Range range = st.top();
14         st.pop();
15         int pivot = arr[range.end];
16         int pos = range.start - 1;
17         for (int i = range.start; i < range.end; ++i)
18             if (arr[i] < pivot)
19                 std::swap(arr[i], arr[++pos]);
20         std::swap(arr[++pos], arr[range.end]);
21         if (pos - 1 > range.start)
22             st.push(Range(range.start, pos - 1));
23         if (pos + 1 < range.end)
24             st.push(Range(pos + 1, range.end));
25     }
26 }
View Code

http://zh.wikipedia.org/zh/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F

另外,加入了randomization的快排:

 

2. 從新排列數組,使得數組左邊爲奇數,右邊爲偶數【時間O(n) 】

 1 void rePartition(vector<int> &arr,int low,int high)
 2 {
 3     int i = low-1;
 4     for (int j = low; j <= high; j++)
 5     {
 6         if (arr[j] % 2 == 1)
 7         {
 8             i++;
 9             swap(arr[i], arr[j]);
10         }
11     }
12 }
View Code

和上面quicksort code1裏的partition一個模子,僅僅修改下判斷條件便可。

3. 從新排列數組,使得數組左邊爲負數,右邊爲正數【時間O(n) 】

 1 void rePartition2(vector<int> &arr, int low, int high)
 2 {
 3     int i = low - 1;
 4     for (int j = low; j <= high; j++)
 5     {
 6         if (arr[j] < 0)
 7         {
 8             i++;
 9             swap(arr[i], arr[j]);
10         }
11     }
12 }
View Code

和上面同樣,依然沿襲一個模子。

4. 升級版:在不改變元素相對順序的基礎上,完成2,3.

 根據ref1ref2 ,應該是不能在O(n)時間,O(1)空間並保持stable順序的限制下完成。這是荷蘭國旗問題的一個變種。若是能借助O(n)空間的話應該很簡單。

想一下如何在O(nlogn)的時間內保證相對順序完成partition吧。ref2裏july有提到。

5. 交錯正負數n

ref

6. Kth Largest Element in an Array

快速選擇(QuickSelection):

 1 class Solution {
 2 public:
 3     int partition(vector<int> &nums, int lo, int hi) {
 4         int pivot = nums[lo];
 5         int i = lo;
 6         for (int j = lo + 1; j <= hi; j++) {
 7             if (nums[j] <= pivot) {
 8                 i++;
 9                 swap(nums[i], nums[j]);
10             }
11         }
12         swap(nums[lo], nums[i]);
13         return i;
14     }
15     
16     int findKthLargest(vector<int> &nums, int k) {
17         k = nums.size() - k;
18         int hi = nums.size() - 1, lo = 0;
19         while (lo < hi) {
20             int j = partition(nums, lo, hi);
21             if (j < k) {
22                 lo = j + 1;
23             } else if (j > k) {
24                 hi = j - 1;
25             } else {
26                 break;
27             }
28         }
29         return nums[k];
30     }
31 };
View Code

對於n個數的數組,一個數x若是從左往右數是第k個數,那麼從右往左數的話是第(n - k + 1)個數。

這裏一開始k = n - k 是按照求第k小的數的方式轉換的。並且!這裏是直接轉換成了下標。由於若是是第x個數的話應該是第n - k + 1個數。下標爲n - k.

 7. partition array

 1 class Solution {
 2 public:
 3     int partitionArray(vector<int> &nums, int k) {
 4         // write your code here
 5         int j = -1;
 6         for (int i = 0; i < nums.size(); i++) {
 7             if (nums[i] < k) {
 8                 j++;
 9                 swap(nums[i], nums[j]);
10             }
11         }
12         return j + 1;
13     }
14 };
View Code

 

<2> 堆排序

1. 堆   reference

咱們通常討論的堆都是二叉堆,它是徹底二叉樹(或近似徹底二叉樹)。

通常都是用數組來表示的,因此 下標爲 i 的結點的父結點下標就爲(i – 1) / 2子結點下標分別爲 2 * i + 1 2 * i + 2

非遞歸版本堆排序: [最小堆]

 1 void minHeapFixDown(vector<int> &nums, int i, int n) {
 2     int j = 2 * i + 1;  // left child
 3     int tmp = nums[i];
 4     while (j < n) {
 5         if (j + 1 < n && nums[j + 1] < nums[j]) {  //取左右孩子中較小的那個
 6             j++;
 7         }
 8         if (nums[j] >= tmp) {
 9             break;
10         }
11         nums[i] = nums[j];
12         i = j;
13         j = 2 * i + 1;
14     }
15     nums[i] = tmp;
16 }
17 
18 void makeMinHeap(vector<int> &nums) {
19     int n = nums.size();
20     for (int i = (n - 2) / 2; i >= 0; i--) {
21         minHeapFixDown(nums, i, nums.size());
22     }
23 }
24 
25 void minHeapSort(vector<int> &nums) {
26     makeMinHeap(nums);
27     for (int i = nums.size() - 1; i >= 1; i--) {
28         swap(nums[i], nums[0]);
29         minHeapFixDown(nums, 0, i);
30     }
31 }
View Code

此外,添加、刪除節點見此:(複雜度均爲O(logN) 

 1 void minHeapFixUp(vector<int> &nums, int i) {
 2     int tmp = nums[i];
 3     int j = (i - 1) / 2;  // parent
 4     while (i != 0 && j >= 0) {
 5         if (nums[j] <= tmp) {
 6             break;
 7         }
 8         nums[i] = nums[j];
 9         i = j;
10         j = (j - 1) / 2;
11     }
12     nums[i] = tmp;
13 }
14 
15 void minHeapInsert(vector<int> &nums, int x) {
16     nums.push_back(x);
17     minHeapFixUp(nums, nums.size() - 1);
18 }
19 
20 int minHeapDelete(vector<int> &nums) {
21     int res = nums[0];
22     swap(nums[0], nums[nums.size() - 1]);    
23     minHeapFixDown(nums, 0, nums.size());
24     return res;
25 }
View Code

 注意:這個是最小堆的實現。用最小堆進行堆排序獲得的是從大到小排列的遞減序列。若想用堆排序獲得遞增序列,須要用最大堆。

最大堆實現:(只須要在最小堆基礎上改兩處,見註釋)

 1 void maxHeapFixDown(vector<int> & nums, int i, int n) {
 2     int j = i * 2 + 1;
 3     int tmp = nums[i];
 4     while (j < n) {
 5         if (j + 1 < n && nums[j + 1] > nums[j]) { // 1
 6             j++;
 7         }
 8         if (nums[j] < tmp) {  // 2
 9             break;
10         }
11         nums[i] = nums[j];
12         i = j;
13         j = j * 2 + 1;
14     }
15     nums[i] = tmp;
16 }
17 
18 void makeMaxHeap(vector<int> &nums) {
19     int n = nums.size();
20     for (int i = (n - 2) / 2; i >= 0; i--) {
21         maxHeapFixDown(nums, i, n);
22     }
23 }
24 
25 void maxHeapSort(vector<int> &nums) {
26     makeMaxHeap(nums);
27     for (int i = nums.size() - 1; i >= 1; i--) {
28         swap(nums[0], nums[i]);
29         maxHeapFixDown(nums, 0, i);
30     }
31 }
View Code

 另外,建堆(初始化)的複雜度是O(N). 證實見此

 

遞歸版本呢?

ref

 

Top K個元素(nlogK):    http://songlee24.github.io/2015/03/21/hua-wei-OJ2051/

能夠用小頂堆的方式,執行n次堆調整;

也能夠用快速選擇。當快速選擇選出第(n-k)小的元素時,它以後的都是比它大的k個元素,即Top K。

 

<3> 歸併排序 Merge

 1 void merge(vector<int>& nums, int lo, int mid, int hi, vector<int>& tmp) {
 2     int i = lo, j = mid + 1;
 3     int m = mid, n = hi;
 4     int k = 0;
 5     while (i <= m && j <= n) {
 6         if (nums[i] <= nums[j]) {
 7             tmp[k++] = nums[i++];
 8         } else {
 9             tmp[k++] = nums[j++];
10         }
11     }
12     while (i <= m) {
13         tmp[k++] = nums[i++];
14     }
15     while (j <= n) {
16         tmp[k++] = nums[j++];
17     }
18     for (int i = 0; i < k; i++) {
19         nums[lo + i] = tmp[i];
20     }
21 }
22 void mergesort(vector<int>& nums, int lo, int hi, vector<int>& tmp) {
23     if (lo < hi) {
24         int mid = lo + (hi - lo) / 2;
25         mergesort(nums, lo, mid, tmp);
26         mergesort(nums, mid + 1, hi, tmp);
27         merge(nums, lo, mid, hi, tmp);
28     }
29 }
View Code

對數組進行歸併排序是須要O(n)的輔助空間的,即一個tmp數組。

 

3.1 求數組中的逆序對[劍36]

 1 //該代碼還有bug
 2 int InversePairs(vector<int> data) {
 3     if (data.size() < 2) {
 4         return 0;
 5     }
 6     vector<int> tmp(data.size(), 0);
 7     return countPairs(data, 0, data.size() - 1, tmp);
 8 }
 9 
10 int countPairs(vector<int>& nums, int lo, int hi, vector<int>& tmp) {
11     if (lo == hi) {
12         tmp[lo] = nums[lo];
13         return 0;
14     }
15     int mid = lo + (hi - lo) / 2;
16     int len = mid - lo + 1;
17     int left = countPairs(nums, lo, mid, tmp);
18     int right = countPairs(nums, mid + 1, hi, tmp);
19     int i = mid, j = hi, k = hi;
20     int cnt = 0;
21     while (i >= lo && j >= mid + 1) {
22         if (nums[i] > nums[j]) {
23             tmp[k--] = nums[i];
View Code

該代碼還有bug。還沒調好。

 

<4> 直接插入排序

 1 void insertionSort(vector<int> &nums) {
 2     int j;
 3     for (int i = 1; i < nums.size(); i++) {
 4         int tmp = nums[i];
 5         for (j = i; j > 0 && tmp < nums[j - 1]; j--) {
 6             nums[j] = nums[j - 1];
 7         }
 8         nums[j] = tmp;
 9     }
10 }
View Code

 須要注意的幾點,都發生在內層for循環內:

(1)內層for循環的 千萬不能在for內部聲明!由於最後nums[j] = tmp處是要用到這個j的。很是容易寫順手把j給定義了。

(2) for循環中間處是j > 0, 不是 >=0 !!! 並且是判斷的 tmp < nums[j - 1],不是nums[j]。

(3)for循環最後總是忘掉j--, 哪來的破毛病,編譯都過不了。

 總之,這個內層for循環的for( ;;)三部分各有一個坑。

<5> 冒泡排序 bubbleSort

1 void bubbleSort(vector<int> &nums) {
2     for (int i = 0; i < nums.size(); i++) {
3         for (int j = 1; j < nums.size() - i; j++) {
4             if (nums[j - 1] > nums[j]) {
5                 swap(nums[j - 1], nums[j]);
6             }
7         }
8     }
9 }
View Code

 冒泡排序每次選一個最大的放到最後一個位置。

第一次選出最大的放到nums[n - 1], 第2次選出次大的放到nums[n - 2],第3次選出第3大的放到nums[n - 3] ...

內層循環每次都是從j = 1到j < n - i 的。

<6> 選擇排序

 1 void insertionSort(vector<int> &nums) {
 2     int j;
 3     for (int i = 0; i < nums.size(); i++) {
 4         int tmp = nums[i];
 5         for (j = i; j > 0 && nums[j - 1] > tmp; j--) {
 6             nums[j] = nums[j - 1];
 7         }
 8         nums[j] = tmp;
 9     }
10 }
View Code

 

 

 

3、最長遞增子序列 LIS

1. DP   O(N^2) 【非最優】

 1 int LIS(vector<int> &v,vector<int> &dp)
 2 {
 3     int lis=0;
 4     for(int i=0;i<dp.size();i++)
 5     {
 6         dp[i]=1;
 7         for(int j=0;j<i;j++)
 8         {
 9             if(v[j]<v[i] && dp[j]+1>dp[i])
10             {
11                 dp[i]=dp[j]+1;
12                 if(dp[i]>lis) lis=dp[i];
13             }
14         }
15     }    
16     return lis;
17 }
View Code

dp[i]表示以v[i]做爲結尾的最長遞增子序列的長度。dp[i]=max{dp[j]+1, 1} where j<i && v[j]<v[i]

ref1

2. DP+二分搜索   O(NlogN) 【最優】

 1 int biSearch(vector<int> &v,int x)
 2 {
 3     int left=0,right=v.size()-1;
 4     while(left<=right)
 5     {
 6         int mid=left+(right-left)/2;
 7         if(x<v[mid])
 8             right=mid-1;
 9         else if(x>v[mid])
10             left=mid+1;
11         else return mid;
12     }
13     return left;
14 }
15 
16 int LIS(vector<int> &v)
17 {
18     vector<int> maxV;
19     maxV.push_back(v[0]);
20     int len=1;
21     for(int i=0;i<v.size();i++)
22     {
23         if(v[i]>maxV[len-1])
24         {
25             len++;
26             maxV.push_back(v[i]);
27         }
28         else
29         {
30             int pos=biSearch(maxV,v[i]);
31             maxV[pos]=v[i];
32         }    
33     }
34     return len;
35 }
View Code

ref2

以上是求出LIS的長度。關於如何輸出具體序列,參考 ref1中的outputLIS。

update refer here    or    here(很簡潔)

 

4、手寫哈希表

  1 #include <iostream>
  2 #include <vector>
  3 #include <string>
  4 #include <fstream>
  5 using namespace std;
  6 
  7 class Record
  8 {
  9 public:
 10     Record();
 11     int getHash(int M);
 12     string getName();
 13     void setName(string key);
 14     void input(ifstream &fin);
 15 private:
 16     string name;
 17     string id_number;
 18 };
 19 Record::Record()
 20 {
 21 }
 22 void Record::input(ifstream &fin)
 23 {
 24 
 25     fin >> name;
 26     fin >> id_number;
 27 }
 28 string Record::getName()
 29 {
 30     return name;
 31 }
 32 void Record::setName(string key)
 33 {
 34     name = key;
 35 }
 36 
 37 int Record::getHash(int M)
 38 {
 39     string key = getName();
 40     int index = 0;
 41     for (int i = 0; i < key.length(); i++)
 42     {
 43         index += key[i];
 44     }
 45     index = index%M;
 46     return index;
 47 }
 48 
 49 class HashTable
 50 {
 51 public:
 52     HashTable(int tableSize);
 53     void insert(Record newRecord);
 54     Record * find(string key);
 55     void erase(Record* pos);
 56     void printTable();
 57 private:
 58     vector<vector<Record>> table;
 59 };
 60 
 61 HashTable::HashTable(int tableSize)
 62 {
 63     table.resize(tableSize);
 64 }
 65 void HashTable::insert(Record newRecord)
 66 {
 67     int index = newRecord.getHash(table.size());
 68     table[index].push_back(newRecord);
 69 }
 70 
 71 Record* HashTable::find(string key)//體現hash性能的關鍵
 72 {
 73     Record tmpRecord;
 74     tmpRecord.setName(key);
 75     int index = tmpRecord.getHash(table.size());
 76     for (int i = 0; i < table[index].size(); i++)
 77     {
 78         if (table[index][i].getName() == key)
 79             return &table[index][i];
 80     }
 81     return NULL;
 82 }
 83 
 84 void HashTable::erase(Record* pos)
 85 {
 86     if (pos == NULL) return;
 87     int index = pos->getHash(table.size());
 88     int i = 0;
 89     while (&table[index][i] != pos && i < table[index].size())
 90         i++;
 91     for (int j = i; j < table[index].size() - 1; j++)
 92         table[index][j] = table[index][j + 1];
 93     table[index].pop_back();
 94 }
 95 
 96 void HashTable::printTable()
 97 {
 98     cout << endl;
 99     for (size_t i = 0; i < table.size(); i++)
100     {
101         for (size_t j = 0; j < table[i].size(); j++)
102         {
103             cout << table[i][j].getName() << ", ";
104         }
105         cout << endl;
106     }
107 }
108 
109 int main()
110 {
111     ifstream fin;
112     fin.open("D:\\fin.txt");
113     HashTable myHash(6);
114     int n;
115     fin >> n;
116     while (n--)//fin裏爲name爲a-z,id隨意的26個數據
117     {
118         Record tmp;
119         tmp.input(fin);
120         myHash.insert(tmp);
121     }
122     myHash.printTable();
123     myHash.erase(myHash.find("j"));
124     myHash.printTable();
125     fin.close();
126 }
View Code

這裏對字符串取哈希時是把字符串裏每個字母對應的ASCII值累加起來,再mod tableSize。

而處理collision的方式是直接鏈式地址法(不過這裏用的不是鏈表,用的是vector)。

ref1            ref2

也能夠參考這裏,基於編程珠璣裏的hash_table實現的。 

 

5、小端、大端

判斷機器是不是大端/小端

小端:低地址放低字節(權重低),高地址放高字節(權重高)。

方法1:

1 bool isLittleEndian() {
2     int num = 0x12345678;
3     char ch = *(char*)(&num);
4     return ch == 0x78;
5 }
View Code

方法2:利用union

聯合體union的存放順序是全部成員都從低地址開始存放,利用該特性能夠輕鬆得到CPU對內存採用Little-endian仍是Big-endian模式

1 bool isBigEndian() {
2     union NUM {
3         int a;
4         char b;
5     }num;
6     num.a = 0x12345678;
7     return (num.b == 0x12);
8 }
View Code

 

小端大端互換

其實就是字節交換而已。就是考察位運算。

 1 #include <iostream>
 2 using namespace std;
 3 
 4 void transfer(int& x) {
 5     char a, b, c, d;
 6     a = (x & 0xff);
 7     b = (x & 0xff00) >> 8;
 8     c = (x & 0xff0000) >> 16;
 9     d = (x & 0xff000000) >> 24;
10     x = (a << 24 | b << 16 | c << 8 | d);
11 }
12 
13 int main() {
14     int x = 0x12345678;
15     cout << hex << x << endl;
16     transfer(x);
17     cout << hex << x << endl;
18 }
View Code

 

 【注意:不少代碼均可以參考soul的手寫】

相關文章
相關標籤/搜索