经典算法回顾
一、排序 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 }
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 }
需要注意的:
每一步都要判断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 }
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 }
和上面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 }
和上面一样,依然沿袭一个模子。
4. 升级版:在不改变元素相对顺序的基础上,完成2,3.
根据ref1和ref2 ,应该是不能在O(n)时间,O(1)空间并保持stable顺序的限制下完成。这是荷兰国旗问题的一个变种。如果能借助O(n)空间的话应该很简单。
想一下如何在O(nlogn)的时间内保证相对顺序完成partition吧。ref2里july有提到。
5. 交错正负数n
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 };
对于n个数的数组,一个数x如果从左往右数是第k个数,那么从右往左数的话是第(n - k + 1)个数。
这里一开始k = n - k 是按照求第k小的数的方式转换的。而且!这里是直接转换成了下标。因为如果是第x个数的话应该是第n - k + 1个数。下标为n - k.
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 };
<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 }
此外,添加、删除节点见此:(复杂度均为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 }
注意:这个是最小堆的实现。用最小堆进行堆排序得到的是从大到小排列的递减序列。若想用堆排序得到递增序列,需要用最大堆。
最大堆实现:(只需要在最小堆基础上改两处,见注释)
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 }
另外,建堆(初始化)的复杂度是O(N). 证明见此。
递归版本呢?
求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 }
对数组进行归并排序是需要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];
该代码还有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 }
需要注意的几点,都发生在内层for循环内:
(1)内层for循环的 j 千万不能在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 }
冒泡排序每次选一个最大的放到最后一个位置。
第一次选出最大的放到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 }
三、最长递增子序列 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 }
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 }
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 }
这里对字符串取哈希时是把字符串里每一个字母对应的ASCII值累加起来,再mod tableSize。
而处理collision的方式是直接链式地址法(不过这里用的不是链表,用的是vector)。
也可以参考这里,基于编程珠玑里的hash_table实现的。
五、小端、大端
判断机器是否是大端/小端
小端:低地址放低字节(权重低),高地址放高字节(权重高)。
方法1:
1 bool isLittleEndian() { 2 int num = 0x12345678; 3 char ch = *(char*)(&num); 4 return ch == 0x78; 5 }
方法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 }
小端大端互换
其实就是字节交换而已。就是考察位运算。
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 }
【注意:很多代码都可以参考soul的手写】