经典算法回顾

一、排序  ref

<1> quicksort及partition相关问题

1. quicksort

code1:[首选]

 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所指元素互换。

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

需要注意的:

每一步都要判断i<j是否成立。

用l和r标识出左右区间。每一次调用l和r都不一样。

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

 

 

 

三、最长递增子序列 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(很简洁)

 

四、手写哈希表

  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实现的。 

 

五、小端、大端

判断机器是否是大端/小端

小端:低地址放低字节(权重低),高地址放高字节(权重高)。

方法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的手写】

posted @ 2015-04-28 08:21  Ryan in C++  阅读(379)  评论(0编辑  收藏  举报