• 经典算法回顾


    一、排序  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的手写】

  • 相关阅读:
    (转)交换两个变量的值,不使用第三个变量的四种法方
    Java权威编码规范
    Git的安装和设置
    ActiveMQ简单入门
    JMS术语
    求助 WPF ListViewItem样式问题
    初步探讨WPF的ListView控件(涉及模板、查找子控件)
    圆角button
    用Inno Setup来解决.NetFramework安装问题
    [!!!!!]Inno Setup教程-常见问题解答
  • 原文地址:https://www.cnblogs.com/forcheryl/p/4462005.html
Copyright © 2020-2023  润新知