STL系列:十个系列

来源:互联网 发布:金星脱口秀 知乎 编辑:程序博客网 时间:2024/06/10 13:07

 

STL系列之一 deque双向队列

分类: STL 他山之石 7949人阅读 评论(8)收藏 举报

deque双向队列是一种双向开口的连续线性空间,可以高效的在头尾两端插入和删除元素,deque在接口上和vector非常相似,下面列出deque的常用成员函数:

 

deque的实现比较复杂,内部会维护一个map(注意!不是STL中的map容器)即一小块连续的空间,该空间中每个元素都是指针,指向另一段(较大的)区域,这个区域称为缓冲区,缓冲区用来保存deque中的数据。因此deque在随机访问和遍历数据会比vector慢。具体的deque实现可以参考《STL源码剖析》,当然此书中使用的SGI STL与VS2008所使用的PJ STL的实现方法还是有区别的。下面给出了deque的结构图:

 

由于篇幅问题,deque的实现细节就不再深入了,下面给出deque的使用范例:

[cpp] view plaincopyprint?
  1. //双向队列 deque   
  2. //by MoreWindows http://blog.csdn.net/morewindows  
  3. #include <deque>   
  4. #include <cstdio>   
  5. #include <algorithm>   
  6. using namespace std;  
  7. int main()  
  8. {  
  9.     deque<int> ideq(20); //Create a deque ideq with 20 elements of default value 0  
  10.     deque<int>::iterator pos;  
  11.     int i;  
  12.   
  13.     //使用assign()赋值  assign在计算机中就是赋值的意思  
  14.     for (i = 0; i < 20; ++i)  
  15.         ideq[i] = i;  
  16.       
  17.     //输出deque   
  18.     printf("输出deque中数据:\n");  
  19.     for (i = 0; i < 20; ++i)  
  20.         printf("%d ", ideq[i]);  
  21.     putchar('\n');  
  22.   
  23.     //在头尾加入新数据   
  24.     printf("\n在头尾加入新数据...\n");  
  25.     ideq.push_back(100);  
  26.     ideq.push_front(i);  
  27.   
  28.     //输出deque   
  29.     printf("\n输出deque中数据:\n");  
  30.     for (pos = ideq.begin(); pos != ideq.end(); pos++)  
  31.         printf("%d ", *pos);  
  32.     putchar('\n');  
  33.   
  34.     //查找   
  35.     const int FINDNUMBER = 19;  
  36.     printf("\n查找%d\n", FINDNUMBER);  
  37.     pos = find(ideq.begin(), ideq.end(), FINDNUMBER);  
  38.     if (pos != ideq.end())  
  39.         printf("find %d success\n", *pos);  
  40.     else  
  41.         printf("find failed\n");  
  42.   
  43.     //在头尾删除数据   
  44.     printf("\n在头尾删除数据...\n");  
  45.     ideq.pop_back();  
  46.     ideq.pop_front();  
  47.   
  48.     //输出deque   
  49.     printf("\n输出deque中数据:\n");  
  50.     for (pos = ideq.begin(); pos != ideq.end(); pos++)  
  51.         printf("%d ", *pos);  
  52.     putchar('\n');  
  53.     return 0;  
  54. }  

运行结果如下:

另外要注意一点。对于deque和vector来说,尽量少用erase(pos)和erase(beg,end)。因为这在中间删除数据后会导致后面的数据向前移动,从而使效率低下。

 

 

STL系列之二 stack栈

分类: STL 他山之石 4131人阅读 评论(4)收藏 举报

栈(statck)这种数据结构在计算机中是相当出名的。栈中的数据是先进后出的(First In Last Out, FILO)。栈只有一个出口,允许新增元素(只能在栈顶上增加)、移出元素(只能移出栈顶元素)、取得栈顶元素等操作。在STL中,栈是以别的容器作为底部结构,再将接口改变,使之符合栈的特性就可以了。因此实现非常的方便。下面就给出栈的函数列表和VS2008中栈的源代码,在STL中栈一共就5个常用操作函数(top()、push()、pop()、 size()、empty() ),很好记的。

 

VS2008中栈的源代码

友情提示:初次阅读时请注意其实现思想,不要在细节上浪费过多的时间。

[cpp] view plaincopyprint?
  1. //VS2008中 stack的定义 MoreWindows整理(http://blog.csdn.net/MoreWindows)  
  2. template<class _Ty, class _Container = deque<_Ty> >  
  3. class stack  
  4. {   // LIFO queue implemented with a container  
  5. public:  
  6.     typedef _Container container_type;  
  7.     typedef typename _Container::value_type value_type;  
  8.     typedef typename _Container::size_type size_type;  
  9.     typedef typename _Container::reference reference;  
  10.     typedef typename _Container::const_reference const_reference;  
  11.   
  12.     stack() : c()  
  13.     {   // construct with empty container  
  14.     }  
  15.   
  16.     explicit stack(const _Container& _Cont) : c(_Cont)  
  17.     {   // construct by copying specified container  
  18.     }  
  19.   
  20.     bool empty() const  
  21.     {   // test if stack is empty  
  22.         return (c.empty());  
  23.     }  
  24.   
  25.     size_type size() const  
  26.     {   // test length of stack   
  27.         return (c.size());  
  28.     }  
  29.   
  30.     reference top()  
  31.     {   // return last element of mutable stack  
  32.         return (c.back());  
  33.     }  
  34.   
  35.     const_reference top() const  
  36.     {   // return last element of nonmutable stack  
  37.         return (c.back());  
  38.     }  
  39.   
  40.     void push(const value_type& _Val)  
  41.     {   // insert element at end  
  42.         c.push_back(_Val);  
  43.     }  
  44.   
  45.     void pop()  
  46.     {   // erase last element   
  47.         c.pop_back();  
  48.     }  
  49.   
  50.     const _Container& _Get_container() const  
  51.     {   // get reference to container  
  52.         return (c);  
  53.     }  
  54.   
  55. protected:  
  56.     _Container c;   // the underlying container  
  57. };  

可以看出,由于栈只是进一步封装别的数据结构,并提供自己的接口,所以代码非常简洁,如果不指定容器,默认是用deque来作为其底层数据结构的(对deque不是很了解?可以参阅《STL系列之一 deque双向队列》)。下面给出栈的使用范例:

[cpp] view plaincopyprint?
  1. //栈 stack支持 empty() size() top() push() pop()  
  2. // by MoreWindows(http://blog.csdn.net/MoreWindows)  
  3. #include <stack>   
  4. #include <vector>   
  5. #include <list>   
  6. #include <cstdio>   
  7. using namespace std;  
  8. int main()  
  9. {  
  10.     //可以使用list或vector作为栈的容器,默认是使用deque的。   
  11.     stack<int, list<int>>      a;  
  12.     stack<int, vector<int>>   b;  
  13.     int i;  
  14.       
  15.     //压入数据   
  16.     for (i = 0; i < 10; i++)  
  17.     {  
  18.         a.push(i);  
  19.         b.push(i);  
  20.     }  
  21.   
  22.     //栈的大小   
  23.     printf("%d %d\n", a.size(), b.size());  
  24.   
  25.     //取栈项数据并将数据弹出栈   
  26.     while (!a.empty())  
  27.     {  
  28.         printf("%d ", a.top());  
  29.         a.pop();  
  30.     }  
  31.     putchar('\n');  
  32.   
  33.     while (!b.empty())  
  34.     {  
  35.         printf("%d ", b.top());  
  36.         b.pop();  
  37.     }  
  38.     putchar('\n');  
  39.     return 0;  
  40. }  

 

转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/6950881

STL系列之三 queue 单向队列

分类: STL 他山之石 3895人阅读 评论(1)收藏 举报

queue单向队列与栈有点类似,一个是在同一端存取数据,另一个是在一端存入数据,另一端取出数据。单向队列中的数据是先进先出(First In First Out,FIFO)。在STL中,单向队列也是以别的容器作为底部结构,再将接口改变,使之符合单向队列的特性就可以了。因此实现也是非常方便的。下面就给出单向队列的函数列表和VS2008中单向队列的源代码。单向队列一共6个常用函数(front()、back()、push()、pop()、empty()、size()),与栈的常用函数较为相似。

 

VS2008中queue单向队列的源代码

友情提示:初次阅读时请注意其实现思想,不要在细节上浪费过多的时间。

[cpp] view plaincopyprint?
  1. <span style="font-size:18px;">//VS2008中 queue的定义 MoreWindows整理(http://blog.csdn.net/MoreWindows)  
  2. template<class _Ty, class _Container = deque<_Ty> >  
  3. class queue  
  4. {   // FIFO queue implemented with a container  
  5. public:  
  6.     typedef _Container container_type;  
  7.     typedef typename _Container::value_type value_type;  
  8.     typedef typename _Container::size_type size_type;  
  9.     typedef typename _Container::reference reference;  
  10.     typedef typename _Container::const_reference const_reference;  
  11.   
  12.     queue() : c()  
  13.     {   // construct with empty container  
  14.     }  
  15.   
  16.     explicit queue(const _Container& _Cont) : c(_Cont)  
  17.     {   // construct by copying specified container  
  18.     }  
  19.   
  20.     bool empty() const  
  21.     {   // test if queue is empty  
  22.         return (c.empty());  
  23.     }  
  24.   
  25.     size_type size() const  
  26.     {   // return length of queue   
  27.         return (c.size());  
  28.     }  
  29.   
  30.     reference front()  
  31.     {   // return first element of mutable queue  
  32.         return (c.front());  
  33.     }  
  34.   
  35.     const_reference front() const  
  36.     {   // return first element of nonmutable queue  
  37.         return (c.front());  
  38.     }  
  39.   
  40.     reference back()  
  41.     {   // return last element of mutable queue  
  42.         return (c.back());  
  43.     }  
  44.   
  45.     const_reference back() const  
  46.     {   // return last element of nonmutable queue  
  47.         return (c.back());  
  48.     }  
  49.   
  50.     void push(const value_type& _Val)  
  51.     {   // insert element at beginning  
  52.         c.push_back(_Val);  
  53.     }  
  54.   
  55.     void pop()  
  56.     {   // erase element at end   
  57.         c.pop_front();  
  58.     }  
  59.   
  60.     const _Container& _Get_container() const  
  61.     {   // get reference to container  
  62.         return (c);  
  63.     }  
  64.   
  65. protected:  
  66.     _Container c;   // the underlying container  
  67. };</span>  

可以看出,由于栈只是进一步封装别的数据结构,并提供自己的接口,所以代码非常简洁,如果不指定容器,默认是用deque来作为其底层数据结构的(对deque不是很了解?可以参阅《STL系列之一deque双向队列》)。下面给出单向队列的使用范例:

[cpp] view plaincopyprint?
  1. //单向队列 queue支持 empty() size() front() back() push() pop()  
  2. //By MoreWindows(http://blog.csdn.net/MoreWindows)  
  3. #include <queue>   
  4. #include <vector>   
  5. #include <list>   
  6. #include <cstdio>   
  7. using namespace std;  
  8.   
  9. int main()  
  10. {  
  11.     //可以使用list作为单向队列的容器,默认是使用deque的。  
  12.     queue<int, list<int>> a;  
  13.     queue<int>        b;  
  14.     int i;  
  15.   
  16.     //压入数据   
  17.     for (i = 0; i < 10; i++)  
  18.     {  
  19.         a.push(i);  
  20.         b.push(i);  
  21.     }  
  22.   
  23.     //单向队列的大小   
  24.     printf("%d %d\n", a.size(), b.size());  
  25.   
  26.     //队列头和队列尾   
  27.     printf("%d %d\n", a.front(), a.back());  
  28.     printf("%d %d\n", b.front(), b.back());  
  29.   
  30.     //取单向队列项数据并将数据移出单向队列   
  31.     while (!a.empty())  
  32.     {  
  33.         printf("%d ", a.front());  
  34.         a.pop();  
  35.     }  
  36.     putchar('\n');  
  37.   
  38.     while (!b.empty())  
  39.     {  
  40.         printf("%d ", b.front());  
  41.         b.pop();  
  42.     }  
  43.     putchar('\n');  
  44.     return 0;  
  45. }  

 

STL系列之四 heap 堆

分类: STL 他山之石 4591人阅读 评论(9)收藏 举报

下面再介绍STL中与堆相关的4个函数——建立堆make_heap(),在堆中添加数据push_heap(),在堆中删除数据pop_heap()和堆排序sort_heap():

头文件 #include <algorithm>

下面的_First与_Last为可以随机访问的迭代器(指针),_Comp为比较函数(仿函数),其规则——如果函数的第一个参数小于第二个参数应返回true,否则返回false。

建立堆

make_heap(_First, _Last, _Comp)

默认是建立最大堆的。对int类型,可以在第三个参数传入greater<int>()得到最小堆。

 

在堆中添加数据

push_heap (_First, _Last)

要先在容器中加入数据,再调用push_heap ()

 

在堆中删除数据

pop_heap(_First, _Last)

要先调用pop_heap()再在容器中删除数据

 

堆排序

sort_heap(_First, _Last)

排序之后就不再是一个合法的heap了

 

有关堆与堆排序的更详细介绍请参阅——《白话经典算法系列之七 堆与堆排序

 

下面给出STL中heap相关函数的使用范例:

[cpp] view plaincopyprint?
  1. //by MoreWindows( http://blog.csdn.net/MoreWindows )  
  2. #include <cstdio>   
  3. #include <vector>   
  4. #include <algorithm>   
  5. #include <functional>   
  6. using namespace std;  
  7. void PrintfVectorInt(vector<int> &vet)  
  8. {  
  9.     for (vector<int>::iterator pos = vet.begin(); pos != vet.end(); pos++)  
  10.         printf("%d ", *pos);  
  11.     putchar('\n');  
  12. }  
  13. int main()  
  14. {  
  15.     const int MAXN = 20;  
  16.     int a[MAXN];  
  17.     int i;  
  18.     for (i = 0; i < MAXN; ++i)  
  19.         a[i] = rand() % (MAXN * 2);  
  20.   
  21.     //动态申请vector 并对vector建堆   
  22.     vector<int> *pvet = new vector<int>(40);  
  23.     pvet->assign(a, a + MAXN);  
  24.   
  25.     //建堆   
  26.     make_heap(pvet->begin(), pvet->end());  
  27.     PrintfVectorInt(*pvet);  
  28.   
  29.     //加入新数据 先在容器中加入,再调用push_heap()  
  30.     pvet->push_back(25);  
  31.     push_heap(pvet->begin(), pvet->end());  
  32.     PrintfVectorInt(*pvet);  
  33.   
  34.     //删除数据  要先调用pop_heap(),再在容器中删除   
  35.     pop_heap(pvet->begin(), pvet->end());  
  36.     pvet->pop_back();  
  37.     pop_heap(pvet->begin(), pvet->end());  
  38.     pvet->pop_back();  
  39.     PrintfVectorInt(*pvet);  
  40.   
  41.     //堆排序   
  42.     sort_heap(pvet->begin(), pvet->end());  
  43.     PrintfVectorInt(*pvet);  
  44.   
  45.     delete pvet;  
  46.     return 0;  
  47. }  

掌握其基本用法后,我们用这个堆排序和《白话经典算法系列》中的堆排序快速排序归并排序来进行个性能测试(Win7 + VS2008 Release下),测试代码如下:

[cpp] view plaincopyprint?
  1. // by MoreWindows( http://blog.csdn.net/MoreWindows )  
  2. #include <cstdio>   
  3. #include <algorithm>   
  4. #include <ctime>   
  5. using namespace std;  
  6. //------------------------快速排序----------------------------  
  7. void quick_sort(int s[], int l, int r)  
  8. {  
  9.     if (l < r)  
  10.     {  
  11.         int i = l, j = r, x = s[l];  
  12.         while (i < j)  
  13.         {  
  14.             while(i < j && s[j] >= x) // 从右向左找第一个小于x的数  
  15.                 j--;    
  16.             if(i < j)   
  17.                 s[i++] = s[j];  
  18.   
  19.             while(i < j && s[i] < x) // 从左向右找第一个大于等于x的数  
  20.                 i++;    
  21.             if(i < j)   
  22.                 s[j--] = s[i];  
  23.         }  
  24.         s[i] = x;  
  25.         quick_sort(s, l, i - 1); // 递归调用   
  26.         quick_sort(s, i + 1, r);  
  27.     }  
  28. }  
  29. //------------------------归并排序----------------------------  
  30. //将有二个有序数列a[first...mid]和a[mid...last]合并。   
  31. void mergearray(int a[], int first, int mid, int last, int temp[])  
  32. {  
  33.     int i = first, j = mid + 1;  
  34.     int m = mid,   n = last;  
  35.     int k = 0;  
  36.   
  37.     while (i <= m && j <= n)  
  38.     {  
  39.         if (a[i] < a[j])  
  40.             temp[k++] = a[i++];  
  41.         else  
  42.             temp[k++] = a[j++];  
  43.     }  
  44.   
  45.     while (i <= m)  
  46.         temp[k++] = a[i++];  
  47.   
  48.     while (j <= n)  
  49.         temp[k++] = a[j++];  
  50.   
  51.     for (i = 0; i < k; i++)  
  52.         a[first + i] = temp[i];  
  53. }  
  54. void mergesort(int a[], int first, int last, int temp[])  
  55. {  
  56.     if (first < last)  
  57.     {  
  58.         int mid = (first + last) / 2;  
  59.         mergesort(a, first, mid, temp);    //左边有序  
  60.         mergesort(a, mid + 1, last, temp); //右边有序  
  61.         mergearray(a, first, mid, last, temp); //再将二个有序数列合并  
  62.     }  
  63. }  
  64. bool MergeSort(int a[], int n)  
  65. {  
  66.     int *p = new int[n];  
  67.     if (p == NULL)  
  68.         return false;  
  69.     mergesort(a, 0, n - 1, p);  
  70.     return true;  
  71. }  
  72. //------------------------堆排序---------------------------  
  73. inline void Swap(int &a, int &b)  
  74. {  
  75.     int c = a;  
  76.     a = b;  
  77.     b = c;  
  78. }  
  79. //建立最小堆   
  80. //  从i节点开始调整,n为节点总数 从0开始计算 i节点的子节点为 2*i+1, 2*i+2  
  81. void MinHeapFixdown(int a[], int i, int n)  
  82. {  
  83.     int j, temp;  
  84.   
  85.     temp = a[i];  
  86.     j = 2 * i + 1;  
  87.     while (j < n)  
  88.     {  
  89.         if (j + 1 < n && a[j + 1] < a[j]) //在左右孩子中找最小的  
  90.             j++;  
  91.   
  92.         if (a[j] >= temp)  
  93.             break;  
  94.   
  95.         a[i] = a[j];     //把较小的子结点往上移动,替换它的父结点  
  96.         i = j;  
  97.         j = 2 * i + 1;  
  98.     }  
  99.     a[i] = temp;  
  100. }  
  101. //建立最小堆   
  102. void MakeMinHeap(int a[], int n)  
  103. {  
  104.     for (int i = n / 2 - 1; i >= 0; i--)  
  105.         MinHeapFixdown(a, i, n);  
  106. }  
  107. void MinheapsortTodescendarray(int a[], int n)  
  108. {  
  109.     for (int i = n - 1; i >= 1; i--)  
  110.     {  
  111.         Swap(a[i], a[0]);  
  112.         MinHeapFixdown(a, 0, i);  
  113.     }  
  114. }  
  115. const int MAXN = 5000000;  
  116. int a[MAXN];  
  117. int b[MAXN], c[MAXN], d[MAXN];  
  118. int main()  
  119. {  
  120.     int i;  
  121.     srand(time(NULL));  
  122.     for (i = 0; i < MAXN; ++i)  
  123.         a[i] = rand() * rand(); //注rand()产生的数在0到65536之间  
  124.   
  125.     for (i = 0; i < MAXN; ++i)  
  126.         d[i] = c[i] = b[i] = a[i];  
  127.   
  128.     clock_t ibegin, iend;  
  129.   
  130.     printf("--当前数据量为%d--By MoreWindows(http://blog.csdn.net/MoreWindows)--\n", MAXN);  
  131.     //快速排序   
  132.     printf("快速排序:  ");  
  133.     ibegin = clock();  
  134.     quick_sort(a, 0, MAXN - 1);  
  135.     iend = clock();  
  136.     printf("%d毫秒\n", iend - ibegin);  
  137.   
  138.       
  139.     //归并排序   
  140.     printf("归并排序:  ");  
  141.     ibegin = clock();  
  142.     MergeSort(b, MAXN);  
  143.     iend = clock();  
  144.     printf("%d毫秒\n", iend - ibegin);  
  145.   
  146.     //堆排序   
  147.     printf("堆排序:  ");  
  148.     ibegin = clock();  
  149.     MakeMinHeap(c, MAXN);  
  150.     MinheapsortTodescendarray(c, MAXN);  
  151.     iend = clock();  
  152.     printf("%d毫秒\n", iend - ibegin);  
  153.   
  154.     //STL中的堆排序   
  155.     printf("STL中的堆排序: ");     
  156.     ibegin = clock();  
  157.     make_heap(d, d + MAXN);  
  158.     sort_heap(d, d + MAXN);  
  159.     iend = clock();  
  160.     printf("%d毫秒\n", iend - ibegin);  
  161.     return 0;  
  162. }  

对100000(十万)个数据的测试结果:

对500000(五十万)个数据的测试结果:

对1000000(一百万)个数据的测试结果:

对5000000(五百万)个数据的测试结果:

从中可以看出快速排序的效率确实要比其它同为O(N * logN)的排序算法要高,而STL中堆操作函数的性能与《白话经典算法系列之七 堆与堆排序》一文中堆操作函数的性能是相差无几的。

 

转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/6967409

STL系列之五 priority_queue 优先级队列

分类: STL 他山之石 4119人阅读 评论(9)收藏 举报

priority_queue 优先级队列是一个拥有权值概念的单向队列queue,在这个队列中,所有元素是按优先级排列的(也可以认为queue是个按进入队列的先后做为优先级的优先级队列——先进入队列的元素优先权要高于后进入队列的元素)。在计算机操作系统中,优先级队列的使用是相当频繁的,进线程调度都会用到。在STL的具体实现中,priority_queue也是以别的容器作为底部结构,再根据堆的处理规则来调整元素之间的位置。下面给出priority_queue的函数列表和VS2008中priority_queue的源代码,本文中与heap有关的函数参见《STL系列之四 heap 堆》。

priority_queue函数列表函数描述      by MoreWindows( http://blog.csdn.net/MoreWindows )构造析构
priority_queue <Elem> c
 创建一个空的queue 。
注:priority_queue构造函数有7个版本,请查阅MSDN
数据访问与增减
c.top() 返回队列头部数据
c.push(elem)在队列尾部增加elem数据
 c.pop()队列头部数据出队
其它操作
c.empty()判断队列是否为空c.size()

返回队列中数据的个数

 

可以看出priority_queue的函数列表与栈stack的函数列表是相同的。

 

VS2008中priority_queue 优先级队列的源代码

友情提示:初次阅读时请注意其实现思想,不要在细节上浪费过多的时间

 

[cpp] view plaincopyprint?
  1. //VS2008中 priority_queue的定义 MoreWindows整理( http://blog.csdn.net/MoreWindows )  
  2. template<class _Ty, class _Container = vector<_Ty>, class _Pr = less<typename _Container::value_type> > //默认以vector为容器的  
  3. class priority_queue  
  4. {   // priority queue implemented with a _Container  
  5. public:  
  6.     typedef _Container container_type;  
  7.     typedef typename _Container::value_type value_type;  
  8.     typedef typename _Container::size_type size_type;  
  9.     typedef typename _Container::reference reference;  
  10.     typedef typename _Container::const_reference const_reference;  
  11.   
  12.     priority_queue() : c(), comp()  
  13.     {   // construct with empty container, default comparator  
  14.     }  
  15.   
  16.     explicit priority_queue(const _Pr& _Pred) : c(), comp(_Pred)  
  17.     {   // construct with empty container, specified comparator  
  18.     }  
  19.   
  20.     priority_queue(const _Pr& _Pred, const _Container& _Cont) : c(_Cont), comp(_Pred)  
  21.     {   // construct by copying specified container, comparator  
  22.         make_heap(c.begin(), c.end(), comp); //参见《STL系列之四 heap 堆的相关函数》  
  23.     }  
  24.   
  25.     template<class _Iter>  
  26.     priority_queue(_Iter _First, _Iter _Last) : c(_First, _Last), comp()  
  27.     {   // construct by copying [_First, _Last), default comparator  
  28.         make_heap(c.begin(), c.end(), comp);  
  29.     }  
  30.   
  31.     template<class _Iter>  
  32.     priority_queue(_Iter _First, _Iter _Last, const _Pr& _Pred) : c(_First, _Last), comp(_Pred)  
  33.     {   // construct by copying [_First, _Last), specified comparator  
  34.         make_heap(c.begin(), c.end(), comp);  
  35.     }  
  36.   
  37.     template<class _Iter>  
  38.     priority_queue(_Iter _First, _Iter _Last, const _Pr& _Pred, const _Container& _Cont) : c(_Cont), comp(_Pred)  
  39.     {   // construct by copying [_First, _Last), container, and comparator  
  40.         c.insert(c.end(), _First, _Last);  
  41.         make_heap(c.begin(), c.end(), comp);  
  42.     }  
  43.   
  44.     bool empty() const  
  45.     {   // test if queue is empty  
  46.         return (c.empty());  
  47.     }  
  48.   
  49.     size_type size() const  
  50.     {   // return length of queue   
  51.         return (c.size());  
  52.     }  
  53.   
  54.     const_reference top() const  
  55.     {   // return highest-priority element  
  56.         return (c.front());  
  57.     }  
  58.   
  59.     reference top()  
  60.     {   // return mutable highest-priority element (retained)  
  61.         return (c.front());  
  62.     }  
  63.   
  64.     void push(const value_type& _Pred)  
  65.     {   // insert value in priority order  
  66.         c.push_back(_Pred);  
  67.         push_heap(c.begin(), c.end(), comp);  
  68.     }  
  69.   
  70.     void pop()  
  71.     {   // erase highest-priority element  
  72.         pop_heap(c.begin(), c.end(), comp);  
  73.         c.pop_back();  
  74.     }  
  75.   
  76. protected:  
  77.     _Container c;   // the underlying container  
  78.     _Pr comp;   // the comparator functor   
  79. };  

下面先给出优级先级队列的使用范例。

[cpp] view plaincopyprint?
  1. //优先级队列 priority_queue by MoreWindows( http://blog.csdn.net/MoreWindows )  
  2. // 支持 empty() size() top() push() pop() 与stack的操作函数全部一样  
  3. //by MoreWindows   
  4. #include <queue>   
  5. #include <list>   
  6. #include <cstdio>   
  7. using namespace std;  
  8. int main()  
  9. {  
  10.     //优先级队列默认是使用vector作容器。   
  11.     priority_queue<int> a;  
  12.     priority_queue<int, list<int>> b; //可以这样声明,但无法使用  
  13.     int i;  
  14.     //压入数据   
  15.     for (i = 0; i < 10; i++)  
  16.     {  
  17.         a.push(i * 2 - 5);  
  18.         //b.push(i); //编译错误   
  19.     }  
  20.     //优先级队列的大小   
  21.     printf("%d\n", a.size());  
  22.     //取优先级队列数据并将数据移出队列   
  23.     while (!a.empty())  
  24.     {  
  25.         printf("%d ", a.top());  
  26.         a.pop();  
  27.     }  
  28.     putchar('\n');  
  29.     return 0;  
  30. }  

下面程序是针对结构体的,对数据的比较是通过对结构体重载operator()。

程序功能是模拟排队过程,每人有姓名和优先级,优先级相同则比较姓名,开始有5个人进入队列,然后队头2个人出队,再有3个人进入队列,最后所有人都依次出队,程序会输出离开队伍的顺序。

[cpp] view plaincopyprint?
  1. //by MoreWindows( http://blog.csdn.net/MoreWindows )  
  2. #include <queue>   
  3. #include <cstring>   
  4. #include <cstdio>   
  5. using namespace std;  
  6. //结构体   
  7. struct Node  
  8. {  
  9.     char szName[20];  
  10.     int  priority;  
  11.     Node(int nri, char *pszName)  
  12.     {  
  13.         strcpy(szName, pszName);  
  14.         priority = nri;  
  15.     }  
  16. };  
  17. //结构体的比较方法 改写operator()   
  18. struct NodeCmp  
  19. {  
  20.     bool operator()(const Node &na, const Node &nb)  
  21.     {  
  22.         if (na.priority != nb.priority)  
  23.             return na.priority <= nb.priority;  
  24.         else  
  25.             return strcmp(na.szName, nb.szName) > 0;  
  26.     }  
  27. };  
  28. void PrintfNode(Node &na)  
  29. {  
  30.     printf("%s %d\n", na.szName, na.priority);  
  31. }  
  32. int main()  
  33. {  
  34.     //优先级队列默认是使用vector作容器,底层数据结构为堆。   
  35.     priority_queue<Node, vector<Node>, NodeCmp> a;  
  36.   
  37.     //有5个人进入队列   
  38.     a.push(Node(5, "小谭"));  
  39.     a.push(Node(3, "小刘"));  
  40.     a.push(Node(1, "小涛"));  
  41.     a.push(Node(5, "小王"));  
  42.   
  43.     //队头的2个人出队   
  44.     PrintfNode(a.top());  
  45.     a.pop();  
  46.     PrintfNode(a.top());  
  47.     a.pop();  
  48.     printf("--------------------\n");  
  49.   
  50.     //再进入3个人   
  51.     a.push(Node(2, "小白"));  
  52.     a.push(Node(2, "小强"));  
  53.     a.push(Node(3, "小新"));  
  54.   
  55.     //所有人都依次出队   
  56.     while (!a.empty())  
  57.     {  
  58.         PrintfNode(a.top());  
  59.         a.pop();  
  60.     }  
  61.   
  62.     return 0;  
  63. }  

读者可以将上面结构体Node改成类来试下,答案2天后发到本篇的评论中。

STL系列之六 set与hash_set

分类: STL 他山之石 5916人阅读 评论(19)收藏 举报

STL系列之六 set与hash_set

set和hash_set是STL中比较重要的容器,有必要对其进行深入了解。在STL中,set是以红黑树(RB-tree)作为底层数据结构的,hash_set是以Hash table(哈希表)作为底层数据结构的。set可以在时间复杂度为O(logN)情况下插入、删除和查找数据。hash_set操作的时间复杂度则比较复杂,这取决于哈希函数和哈希表的负载情况。下面列出set和hash_set的常用函数:

 

set和hase_set的更多函数请查阅MSDN

 

set的使用范例如下(hash_set类似):

[cpp] view plaincopyprint?
  1. // by MoreWindows( http://blog.csdn.net/MoreWindows )  
  2. #include <set>   
  3. #include <ctime>   
  4. #include <cstdio>   
  5. using namespace std;  
  6.   
  7. int main()  
  8. {  
  9.     printf("--set使用 by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");  
  10.     const int MAXN = 15;  
  11.     int a[MAXN];  
  12.     int i;  
  13.     srand(time(NULL));  
  14.     for (i = 0; i < MAXN; ++i)  
  15.         a[i] = rand() % (MAXN * 2);  
  16.   
  17.     set<int> iset;     
  18.     set<int>::iterator pos;   
  19.   
  20.     //插入数据 insert()有三种重载   
  21.     iset.insert(a, a + MAXN);  
  22.   
  23.     //当前集合中个数 最大容纳数据量   
  24.     printf("当前集合中个数: %d     最大容纳数据量: %d\n", iset.size(), iset.max_size());  
  25.   
  26.     //依次输出   
  27.     printf("依次输出集合中所有元素-------\n");  
  28.     for (pos = iset.begin(); pos != iset.end(); ++pos)  
  29.         printf("%d ", *pos);  
  30.     putchar('\n');  
  31.   
  32.     //查找   
  33.     int findNum = MAXN;  
  34.     printf("查找 %d是否存在-----------------------\n", findNum);  
  35.     pos = iset.find(findNum);  
  36.     if (pos != iset.end())  
  37.         printf("%d 存在\n", findNum);  
  38.     else  
  39.         printf("%d 不存在\n", findNum);  
  40.   
  41.     //在最后位置插入数据,如果给定的位置不正确,会重新找个正确的位置并返回该位置  
  42.     pos  = iset.insert(--iset.end(), MAXN * 2);   
  43.     printf("已经插入%d\n", *pos);  
  44.   
  45.     //删除   
  46.     iset.erase(MAXN);  
  47.     printf("已经删除%d\n", MAXN);  
  48.   
  49.     //依次输出   
  50.     printf("依次输出集合中所有元素-------\n");  
  51.     for (pos = iset.begin(); pos != iset.end(); ++pos)  
  52.         printf("%d ", *pos);  
  53.     putchar('\n');  
  54.     return 0;  
  55. }  

运行结果如下:

 

下面试下在set中使用类(结构体也可以类似这样做)。这个类很简单,只有一个成员变量,及设置和获取这个成员变量的成员函数。

[cpp] view plaincopyprint?
  1. //在set中使用类要重载‘<’并实现拷贝构造函数   
  2. // by MoreWindows( http://blog.csdn.net/MoreWindows )  
  3. #include <set>   
  4. #include <ctime>   
  5. #include <cstdio>   
  6. using namespace std;  
  7. class Node  
  8. {  
  9. public:  
  10.     Node(int nAge = 0)  
  11.     {  
  12.         m_nAge = nAge;  
  13.     }  
  14.     Node(const Node &na)  //拷贝构造函数  
  15.     {  
  16.         m_nAge = na.GetAge();  
  17.     }  
  18.     int GetAge()  
  19.     {  
  20.         return m_nAge;  
  21.     }  
  22. private:  
  23.     int m_nAge;  
  24. };  
  25. //不能写成类的成员函数   
  26. inline bool operator < (const Node &na, const Node &nb)   
  27. {  
  28.     return na.GetAge() < nb.GetAge();  
  29. }  
  30. int main()  
  31. {  
  32.     int i;  
  33.     set<Node> nset;  
  34.     for (i = 0; i < MAXN; ++i)  
  35.         nset.insert(Node(i));  
  36.     return 0;  
  37. }  

编译,直接报了3个错误!!1个在拷贝构造函数,2个在operator<()函数。如下图所示:

3个错误都是一样的:

error C2662: “Node::GetAge”: 不能将“this”指针从“const Node”转换为“Node &” 转换丢失限定符

这是怎么回事呀?分析下,拷贝构造函数与operator<()函数出错,错误都指向了GetAge()函数,有点古怪,比较下它们与GetAge()函数,可以发现最大的不同点在于这2个函数都用到了const而GetAge()函数没有使用const。难道是这个导致报错了吗?先给GetAge()函数加个const看看,如下:

       int GetAge()  const //增加这个const

       {

              returnm_nAge;

       }

再编译,不报错了。再查下资料,原因如下——因为那2个函数都使用了const修饰的对象,但GetAge()没有加上const以保证它不修改对象,编译器认为这种写法是不安全的,所以就毫不犹豫报了个错误。

这种错误如果不亲身体会下,到笔试面试时很可能写了个错误程序而自己还处于一无所知中(死在这些小细节上最不值得)。另外,如果使用VC6.0则不会提示详细的错误信息——“转换丢失限定符”。

 

STL还为set提供了一些集合运算的函数,如交集set_intersection()、并集set_union()、差集set_difference()和对称差集set_symmetric_difference()。这些就不详细介绍了,有兴趣可以自己动手试一试。

下面开始对set和hash_set作个性能测试(Win7 +VS2008Release下)。

测试代码如下:

[cpp] view plaincopyprint?
  1. // by MoreWindows( http://blog.csdn.net/MoreWindows )  
  2. #include <set>   
  3. #include <hash_set>   
  4. #include <iostream>   
  5. #include <ctime>   
  6. #include <cstdio>   
  7. #include <cstdlib>   
  8. using namespace std;  
  9. using namespace stdext;  //hash_set  
  10.   
  11. // MAXN个数据 MAXQUERY次查询   
  12. const int MAXN = 10000, MAXQUERY = 5000000;  
  13. int a[MAXN], query[MAXQUERY];  
  14.   
  15. void PrintfContainertElapseTime(char *pszContainerName, char *pszOperator, long lElapsetime)  
  16. {  
  17.     printf("%s 的%s操作 用时 %d毫秒\n", pszContainerName, pszOperator, lElapsetime);  
  18. }  
  19.   
  20. int main()  
  21. {  
  22.     printf("set VS hash_set 性能测试 数据容量 %d个 查询次数 %d次\n", MAXN, MAXQUERY);  
  23.     const int MAXNUM = MAXN * 4;  
  24.     const int MAXQUERYNUM = MAXN * 4;  
  25.     printf("容器中数据范围 [0, %d) 查询数据范围[0, %d)\n", MAXNUM, MAXQUERYNUM);  
  26.     printf("--by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");  
  27.       
  28.     //随机生成在[0, MAXNUM)范围内的MAXN个数   
  29.     int i;  
  30.     srand(time(NULL));  
  31.     for (i = 0; i < MAXN; ++i)  
  32.         a[i] = (rand() * rand()) % MAXNUM;  
  33.     //随机生成在[0, MAXQUERYNUM)范围内的MAXQUERY个数  
  34.     srand(time(NULL));  
  35.     for (i = 0; i < MAXQUERY; ++i)  
  36.         query[i] = (rand() * rand()) % MAXQUERYNUM;  
  37.   
  38.     set<int>       nset;  
  39.     hash_set<int> nhashset;  
  40.     clock_t  clockBegin, clockEnd;  
  41.   
  42.   
  43.     //insert   
  44.     printf("-----插入数据-----------\n");  
  45.   
  46.     clockBegin = clock();    
  47.     nset.insert(a, a + MAXN);   
  48.     clockEnd = clock();  
  49.     printf("set中有数据%d个\n", nset.size());  
  50.     PrintfContainertElapseTime("set""insert", clockEnd - clockBegin);  
  51.   
  52.     clockBegin = clock();    
  53.     nhashset.insert(a, a + MAXN);   
  54.     clockEnd = clock();  
  55.     printf("hash_set中有数据%d个\n", nhashset.size());  
  56.     PrintfContainertElapseTime("hase_set""insert", clockEnd - clockBegin);  
  57.   
  58.   
  59.     //find   
  60.     printf("-----查询数据-----------\n");  
  61.   
  62.     int nFindSucceedCount, nFindFailedCount;   
  63.     nFindSucceedCount = nFindFailedCount = 0;  
  64.     clockBegin = clock();   
  65.     for (i = 0; i < MAXQUERY; ++i)  
  66.         if (nset.find(query[i]) != nset.end())  
  67.             ++nFindSucceedCount;  
  68.         else  
  69.             ++nFindFailedCount;  
  70.     clockEnd = clock();  
  71.     PrintfContainertElapseTime("set""find", clockEnd - clockBegin);  
  72.     printf("查询成功次数: %d    查询失败次数: %d\n", nFindSucceedCount, nFindFailedCount);  
  73.       
  74.     nFindSucceedCount = nFindFailedCount = 0;  
  75.     clockBegin = clock();    
  76.     for (i = 0; i < MAXQUERY; ++i)  
  77.         if (nhashset.find(query[i]) != nhashset.end())  
  78.             ++nFindSucceedCount;  
  79.         else  
  80.             ++nFindFailedCount;  
  81.     clockEnd = clock();  
  82.     PrintfContainertElapseTime("hash_set""find", clockEnd - clockBegin);  
  83.     printf("查询成功次数: %d    查询失败次数: %d\n", nFindSucceedCount, nFindFailedCount);  
  84.     return 0;  
  85. }  

在数据容量100万,查询次数500万时,程序运行结果如下:

由于查询的失败次数太多,这次将查询范围变小使用再测试下:

由于结点过多,80多万个结点,set的红黑树树高约为19(2^19=524288,2^20=1048576),查询起来还是比较费时的。hash_set在时间性能上比set要好一些,并且如果查询成功的几率比较大的话,hash_set会有更好的表现。想知道为什么hash_set会有优良的性能表现,请看继集——《STL系列之九 探索hash_set》。

 

 

注1.   MSDN上讲set的erase()是有返回值的,但在VS2008中查看set的源代码,erase()函数的三个重载版本中,有二个返回值都为void即无返回值,另一个返回size_type。 可以通过http://msdn.microsoft.com/zh-cn/library/8h4a3515(v=VS.90).aspx查看MSDN上对set的erase()说明。

STL系列之七 快速计算x的n次幂 power()的实现

分类: STL 他山之石 3697人阅读 评论(22)收藏 举报

计算x的n次幂最简单直接的方法就是相乘n次,很容易写出程序:

[cpp] view plaincopyprint?
  1. //计算x^n 直接乘n次 by MoreWindows( http://blog.csdn.net/MoreWindows )  
  2. int power1(int x, unsigned int n)  
  3. {  
  4.     int result = 1;  
  5.     while (n--)  
  6.         result *= x;  
  7.     return result;  
  8. }  

这种计算的效率显然不高,我们可以用二分法来加速计算x^n=x^(n/2)* x^(n/2)即x^10=x^5*x^5,这种计算N次幂只要相乘O(logN)次。运用递归的方法不难写出:

[cpp] view plaincopyprint?
  1. //计算x^n 二分递归实现  by MoreWindows( http://blog.csdn.net/MoreWindows )  
  2. int power2(int x, unsigned int n)  
  3. {  
  4.     if (n == 0)  
  5.         return 1;  
  6.     else if (n == 1)  
  7.         return x;  
  8.     else   
  9.     {  
  10.         if (n % 2 == 1)  
  11.             return power2(x, n / 2) * power2(x, n / 2) * x;  
  12.         else  
  13.             return power2(x, n / 2) * power2(x, n / 2);  
  14.     }  
  15. }  

递归毕竟比较浪费时间,且会有很多重复计算。

因此最好能换成非递归的方式来实现二分法。

考虑x^23,可以先从x ->x^2 -> x^4 -> x^8 -> x^16 取result1 = x^16,然后23-16=7。

我们只要计算x^7再与result1相乘就可以得到x^23。对于x^7也可以采用这种方法

取result2 = x^4,然后7-4=3,只要计算x^3再与result2相乘就可以得到x^7。由此可以将x^23写成x^16 * x^4* x^2 * x,即23=16+4+2+1,而23 = 10111(二进制),所以只要将n化为二进制并由低位到高位依次判断如果第i位为1,则result *=x^(2^i)。

函数实现如下:

[cpp] view plaincopyprint?
  1. //计算x^n   by MoreWindows( http://blog.csdn.net/MoreWindows )  
  2. int power3(int x, unsigned int n)  
  3. {  
  4.     if (n == 0)  
  5.         return 1;  
  6.     int result = 1;  
  7.     while (n != 0)  
  8.     {  
  9.         if ((n & 1) != 0)  
  10.             result *= x;  
  11.         x *= x;  
  12.         n >>= 1;  
  13.     }  
  14.     return result;  
  15. }  

此函数可以在相乘O(logN)次内计算x的n次幂,且避免了重复计算。但还可以作进一步的优化,如像48=110000(二进制)这种低位有很多0的数,可以先过滤掉低位的0再进行计算,这样也会提高一些效率。程序如下:

[cpp] view plaincopyprint?
  1. //计算x^n  by MoreWindows( http://blog.csdn.net/MoreWindows )  
  2. int power4(int x, unsigned int n)  
  3. {  
  4.     if (n == 0)  
  5.     {  
  6.         return 1;  
  7.     }  
  8.     else  
  9.     {  
  10.         while ((n & 1) == 0)  
  11.         {  
  12.             n >>= 1;  
  13.             x *= x;  
  14.         }  
  15.     }  
  16.     int result = x;  
  17.     n >>= 1;  
  18.     while (n != 0)  
  19.     {     
  20.         x *= x;  
  21.         if ((n & 1) != 0)  
  22.             result *= x;  
  23.         n >>= 1;  
  24.     }  
  25.     return result;  
  26. }  

验证一下

[cpp] view plaincopyprint?
  1. int main()  
  2. {  
  3.     printf("验证power4()  -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");  
  4.     for (int i = 0; i <= 10; i++)  
  5.         printf("2的%d次方为\t%d\n", i, power4(2, i));  
  6.     return 0;  
  7. }  

结果为

 
看到这里,理解STL的power()函数应该就是个水到渠成的事情了——我们自己写的power4()正是STL的power()函数。

 

 注,非常感谢网友evaxiao帮我找出了power4()的一个错误,我已经在文中改正了,谢谢网友evaxiao

 

转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/7174143

 

STL系列之八 slist单链表

分类: STL 他山之石 3154人阅读 评论(8)收藏 举报

微软的VS208所使用的PJ STL(注1)中的list是双链表,但在某些场合,一个轻量级的单链表会更加合适。单链表非常常见,这里就不去细说了,本文的slist(single linked list)单链表实现了链表的基本功能,如有需要,以后还会扩充的。slist单链表(带头结点)的示意图如下所示:

完整的C++代码如下:

[cpp] view plaincopyprint?
  1. //带头结点的单链表      
  2. //by MoreWindows( http://blog.csdn.net/MoreWindows )    
  3. template<class T>  
  4. struct Node  
  5. {  
  6.     T val;  
  7.     Node *next;  
  8.     Node(T &n)  
  9.     {  
  10.         this->val = n;  
  11.         this->next = NULL;  
  12.     }  
  13. };  
  14. template<class T>  
  15. class slist  
  16. {  
  17. public:  
  18.     slist();  
  19.     ~slist();  
  20.     void push_front(T &t);  
  21.     bool find(T &t);  
  22.     bool remove(T &t);  
  23.     bool removeAll(T &t);  
  24.     void clear();  
  25.     int size();  
  26. public:  
  27.     int     m_nListDataCount;  
  28.     Node<T> *m_head;  
  29. };  
  30. template<class T>  
  31. slist<T>::slist()  
  32. {  
  33.     m_head = NULL;  
  34.     m_nListDataCount = 0;  
  35. }  
  36. template<class T>  
  37. slist<T>::~slist()  
  38. {  
  39.     Node<T> *p, *pnext;  
  40.     for (p = m_head; p != NULL; p = pnext)  
  41.     {  
  42.         pnext = p->next;  
  43.         free(p);  
  44.     }  
  45.     m_nListDataCount = 0;  
  46. }  
  47. template<class T>  
  48. void slist<T>::push_front(T &t)  
  49. {         
  50.     Node<T> *pNode = (Node<T> *)malloc(sizeof(Node<T>));  
  51.     pNode->val = t;  
  52.     pNode->next = m_head;  
  53.     m_head = pNode;  
  54.     m_nListDataCount++;  
  55. }  
  56. template<class T>  
  57. bool slist<T>::find(T &t)  
  58. {  
  59.     for (Node<T> *p = m_head; p != NULL; p = p->next)  
  60.         if (p->val == t)  
  61.             return true;  
  62.   
  63.     return false;  
  64. }  
  65. template<class T>  
  66. int slist<T>::size()  
  67. {  
  68.     return m_nListDataCount;  
  69. }  
  70. //删除链表中第一个值为t的结点   
  71. template<class T>  
  72. bool slist<T>::remove(T &t)  
  73. {  
  74.     Node<T> *pNode, *pPreNode;  
  75.     pPreNode = pNode = m_head;  
  76.     while (pNode != NULL)  
  77.     {  
  78.         if (pNode->val == t)  
  79.         {  
  80.             if (pPreNode != pNode)  
  81.                 pPreNode->next = pNode->next;  
  82.             else  
  83.                 m_head = NULL;  
  84.             free(pNode);  
  85.             m_nListDataCount--;  
  86.             return true;  
  87.         }  
  88.         pPreNode = pNode;  
  89.         pNode = pNode->next;  
  90.     }  
  91.     return false;  
  92. }  
  93. //会删除链表中所有值为t的结点   
  94. template<class T>  
  95. bool slist<T>::removeAll(T &t)  
  96. {  
  97.     bool flagDeleteNode = false;  
  98.     Node<T> *pNode, *pPreNode;  
  99.     pPreNode = pNode = m_head;  
  100.     while (pNode != NULL)  
  101.     {  
  102.         if (pNode->val == t)  
  103.         {  
  104.             pPreNode->next = pNode->next;  
  105.             free(pNode);  
  106.             pNode = pPreNode->next;  
  107.             m_nListDataCount--;  
  108.             flagDeleteNode = true;  
  109.         }  
  110.         else  
  111.         {  
  112.             pPreNode = pNode;  
  113.             pNode = pNode->next;  
  114.         }  
  115.     }  
  116.     return flagDeleteNode;  
  117. }  
  118. template<class T>  
  119. void slist<T>::clear()  
  120. {  
  121.     Node<T> *cur = m_head;  
  122.     while (cur != NULL)  
  123.     {  
  124.         Node<T> *next = cur->next;  
  125.         free(cur);  
  126.         cur = next;  
  127.     }  
  128.     m_head = NULL;  
  129. }  

该slist完成了从头部插入,查找和删除数据等链表的基本操作,下一篇将使用这个slist来完成一个哈希表,请关注下一篇——《STL系列之九 探索hash_set》

 

注1.STL分为很多版本,微软的VS系列使用的是PJ STL。而《STL源码剖析》书中主要使用SGI STL。

STL系列之九 探索hash_set

分类: STL 他山之石 4180人阅读 评论(13)收藏 举报

Title:        STL系列之九 探索hash_set

Author:     MoreWindows

Blog:       http://blog.csdn.net/MoreWindows

E-mail:     morewindows@126.com

KeyWord:   C++ STL set hash_set 哈希表 链地址法

 

本文将着重探索hash_set比set快速高效的原因,阅读本文前,推荐先阅读本文的姊妹篇《STL系列之六 set与hash_set

一.hash_set之基石——哈希表

    hash_set的底层数据结构是哈希表,因此要深入了解hash_set,必须先分析哈希表。哈希表是根据关键码值(Key-Value)而直接进行访问的数据结构,它用哈希函数处理数据得到关键码值,关键码值对应表中一个特定位置再由应该位置来访问记录,这样可以在时间复杂性度为O(1)内访问到数据。但是很有可能出现多个数据经哈希函数处理后得到同一个关键码——这就产生了冲突,解决冲突的方法也有很多,各大数据结构教材及考研辅导书上都会介绍大把方法。这里采用最方便最有效的一种——链地址法,当有冲突发生时将具同一关键码的数据组成一个链表。下图展示了链地址法的使用:

二.简化版的hash_table

    按照上面的分析和图示,并参考《编程珠玑》第15章中哈希表的实现,不难写出一个简单的哈希表,我们称之为简化版hash_table。该哈希表由一个指针数组组成,数组中每个元素都是链表的表头指针,程序分为hash_table.h,hash_table.cpp和main.cpp。

1.hash_table.h

[cpp] view plaincopyprint?
  1. #pragma once   
  2. #define NULL 0   
  3. //简化版hash_table    
  4. //by MoreWindows( http://blog.csdn.net/MoreWindows )  
  5. struct Node  
  6. {  
  7.     int val;  
  8.     Node *next;  
  9.     Node(int n)  
  10.     {  
  11.         this->val = n;  
  12.         this->next = NULL;  
  13.     }  
  14. };  
  15. class hash_table  
  16. {  
  17. public:  
  18.     hash_table(const int ntablesize);  
  19.     ~hash_table();  
  20.     bool insert(int n);  
  21.     void insert(int *pFirst, int *pLast);  
  22.     bool find(int n);  
  23.     int  size();  
  24.     int HashFun(int n);  
  25. public:  
  26.     int      m_nTableSize;  
  27.     int      m_nTableDataCount;  
  28.     Node**   m_ppTable;  
  29. };  

2.hash_table.cpp

[cpp] view plaincopyprint?
  1. //简化版hash_table   
  2. //by MoreWindows( http://blog.csdn.net/MoreWindows )  
  3. #include "hash_table.h"   
  4. #include <malloc.h>   
  5. #include <memory.h>   
  6. hash_table::hash_table(const int ntablesize)  
  7. {  
  8.     m_nTableSize = ntablesize;  
  9.     m_ppTable = (Node**)malloc(sizeof(Node*) * m_nTableSize);  
  10.     if (m_ppTable == NULL)  
  11.         return ;  
  12.     m_nTableDataCount = 0;  
  13.     memset(m_ppTable, 0, sizeof(Node*) * m_nTableSize);  
  14. }  
  15. hash_table::~hash_table()  
  16. {  
  17.     free(m_ppTable);  
  18.     m_ppTable = NULL;  
  19.     m_nTableDataCount = 0;  
  20.     m_nTableSize = 0;  
  21. }  
  22. int inline hash_table::HashFun(int n)   
  23. {  
  24.     return (n ^ 0xdeadbeef) % m_nTableSize;  
  25. }  
  26. int hash_table::size()  
  27. {  
  28.     return m_nTableDataCount;  
  29. }  
  30. bool hash_table::insert(int n)  
  31. {  
  32.     int key = HashFun(n);  
  33.     //在该链表中查找该数是否已经存在   
  34.     for (Node *p = m_ppTable[key]; p != NULL; p = p->next)  
  35.         if (p->val == n)  
  36.             return true;  
  37.     //在链表的头部插入   
  38.     Node *pNode = new Node(n);  
  39.     if (pNode == NULL)  
  40.         return false;  
  41.     pNode->next = m_ppTable[key];  
  42.     m_ppTable[key] = pNode;  
  43.     m_nTableDataCount++;  
  44.     return true;  
  45. }  
  46. bool hash_table::find(int n)  
  47. {  
  48.     int key = HashFun(n);  
  49.     for (Node *pNode = m_ppTable[key]; pNode != NULL; pNode = pNode->next)  
  50.         if (pNode->val == n)  
  51.             return true;  
  52.     return false;  
  53. }  
  54. void hash_table::insert(int *pFirst, int *pLast)  
  55. {  
  56.     for (int *p = pFirst; p != pLast; p++)  
  57.         this->insert(*p);  
  58. }  

3.main.cpp

 在main.cpp中,对set、hash_set、简化版hash_table作一个性能测试,测试环境为Win7+VS2008的Release设置(下同)。

[cpp] view plaincopyprint?
  1. //测试set,hash_set及简化版hash_table   
  2. // by MoreWindows( http://blog.csdn.net/MoreWindows )  
  3. #include <set>   
  4. #include <hash_set>   
  5. #include "hash_table.h"   
  6. #include <iostream>   
  7. #include <ctime>   
  8. #include <cstdio>   
  9. #include <cstdlib>   
  10. using namespace std;  
  11. using namespace stdext;  //hash_set  
  12. void PrintfContainerElapseTime(char *pszContainerName, char *pszOperator, long lElapsetime)  
  13. {  
  14.     printf("%s 的 %s操作 用时 %d毫秒\n", pszContainerName, pszOperator, lElapsetime);  
  15. }  
  16. // MAXN个数据 MAXQUERY次查询   
  17. const int MAXN = 5000000, MAXQUERY = 5000000;  
  18. int a[MAXN], query[MAXQUERY];  
  19. int main()  
  20. {  
  21.     printf("set VS hash_set VS hash_table(简化版) 性能测试\n");  
  22.     printf("数据容量 %d个 查询次数 %d次\n", MAXN, MAXQUERY);  
  23.     const int MAXNUM = MAXN * 4;  
  24.     const int MAXQUERYNUM = MAXN * 4;  
  25.     printf("容器中数据范围 [0, %d) 查询数据范围[0, %d)\n", MAXNUM, MAXQUERYNUM);  
  26.     printf("--by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");  
  27.       
  28.     //随机生成在[0, MAXNUM)范围内的MAXN个数   
  29.     int i;  
  30.     srand((unsigned int)time(NULL));  
  31.     for (i = 0; i < MAXN; ++i)  
  32.         a[i] = (rand() * rand()) % MAXNUM;  
  33.     //随机生成在[0, MAXQUERYNUM)范围内的MAXQUERY个数  
  34.     srand((unsigned int)time(NULL));  
  35.     for (i = 0; i < MAXQUERY; ++i)  
  36.         query[i] = (rand() * rand()) % MAXQUERYNUM;  
  37.   
  38.     set<int>       nset;  
  39.     hash_set<int>  nhashset;  
  40.     hash_table   nhashtable(MAXN + 123);  
  41.     clock_t  clockBegin, clockEnd;  
  42.   
  43.     //insert   
  44.     printf("-----插入数据-----------\n");  
  45.     clockBegin = clock();      
  46.     nset.insert(a, a + MAXN);   
  47.     clockEnd = clock();  
  48.     printf("set中有数据%d个\n", nset.size());  
  49.     PrintfContainerElapseTime("set""insert", clockEnd - clockBegin);  
  50.   
  51.     clockBegin = clock();    
  52.     nhashset.insert(a, a + MAXN);   
  53.     clockEnd = clock();  
  54.     printf("hash_set中有数据%d个\n", nhashset.size());  
  55.     PrintfContainerElapseTime("hash_set""insert", clockEnd - clockBegin);  
  56.   
  57.     clockBegin = clock();  
  58.     for (i = 0; i < MAXN; i++)  
  59.         nhashtable.insert(a[i]);   
  60.     clockEnd = clock();  
  61.     printf("hash_table中有数据%d个\n", nhashtable.size());  
  62.     PrintfContainerElapseTime("Hash_table""insert", clockEnd - clockBegin);  
  63.   
  64.     //find   
  65.     printf("-----查询数据-----------\n");  
  66.     int nFindSucceedCount, nFindFailedCount;   
  67.     nFindSucceedCount = nFindFailedCount = 0;  
  68.     clockBegin = clock();   
  69.     for (i = 0; i < MAXQUERY; ++i)  
  70.         if (nset.find(query[i]) != nset.end())  
  71.             ++nFindSucceedCount;  
  72.         else  
  73.             ++nFindFailedCount;  
  74.     clockEnd = clock();  
  75.     PrintfContainerElapseTime("set""find", clockEnd - clockBegin);  
  76.     printf("查询成功次数: %d    查询失败次数: %d\n", nFindSucceedCount, nFindFailedCount);  
  77.       
  78.     nFindSucceedCount = nFindFailedCount = 0;  
  79.     clockBegin = clock();    
  80.     for (i = 0; i < MAXQUERY; ++i)  
  81.         if (nhashset.find(query[i]) != nhashset.end())  
  82.             ++nFindSucceedCount;  
  83.         else  
  84.             ++nFindFailedCount;  
  85.     clockEnd = clock();  
  86.     PrintfContainerElapseTime("hash_set""find", clockEnd - clockBegin);  
  87.     printf("查询成功次数: %d    查询失败次数: %d\n", nFindSucceedCount, nFindFailedCount);  
  88.   
  89.     nFindSucceedCount = nFindFailedCount = 0;  
  90.     clockBegin = clock();    
  91.     for (i = 0; i < MAXQUERY; ++i)  
  92.         if (nhashtable.find(query[i]))  
  93.             ++nFindSucceedCount;  
  94.         else  
  95.             ++nFindFailedCount;  
  96.     clockEnd = clock();  
  97.     PrintfContainerElapseTime("hash_table""find", clockEnd - clockBegin);  
  98.     printf("查询成功次数: %d    查询失败次数: %d\n", nFindSucceedCount, nFindFailedCount);  
  99.     return 0;  
  100. }  

在数据量为500万时测试结果如下:

从程序运行结果可以发现,我们自己实现的hash_table(简化版)在插入和查找的效率要远高于set。为了进一步分析,最好能统计hash_table中的各个链表的长度情况,这样可以有效的了解平均每次查找要访问多少个数据。写出统计hash_table各链表长度的函数如下:

[cpp] view plaincopyprint?
  1. // by MoreWindows( http://blog.csdn.net/MoreWindows )  
  2. void StatisticHashTable(hash_table &ht)  
  3. {  
  4.     const int MAXLISTLINE = 100;  
  5.     int i, a[MAXLISTLINE], nExtendListNum;  
  6.     nExtendListNum = 0;  
  7.     memset(a, 0, sizeof(a[0]) * MAXLISTLINE);  
  8.     for (i = 0; i < ht.m_nTableSize; i++)  
  9.     {  
  10.         int sum = 0;  
  11.         for (Node *p = ht.m_ppTable[i]; p != NULL; p = p->next)  
  12.             ++sum;  
  13.         if (sum >= MAXLISTLINE)  
  14.             nExtendListNum++;  
  15.         else  
  16.             a[sum]++;  
  17.     }  
  18.     printf("hash_table中链表长度统计:\n");  
  19.     for (i = 0; i < MAXLISTLINE; i++)  
  20.         if (a[i] > 0)  
  21.         {  
  22.             printf("   长度为%d的链表有%d个 这些链表中数据占总数据的%.2lf%%\n", i, a[i], (a[i] * i * 100.0) / ht.size());  
  23.         }  
  24.         printf("   长度超过%d的链表有%d个\n", MAXLISTLINE, nExtendListNum);  
  25. }  

用此段代码得到链表长度的统计结果:

可以发现在hash_table中最长的链表也只有5个元素,长度为1和长度为2的链表中的数据占全部数据的89%以上。因此绝大数查询将仅仅访问哈希表1次到2次。这样的查询效率当然会比set(内部使用红黑树——类似于二叉平衡树)高的多。有了这个图示,无疑已经可以证明hash_set会比set快速高效了。但hash_set还可以动态的增加表的大小,因此我们再实现一个表大小可增加的hash_table。

三.强化版hash_table

    首先来看看VS2008中hash_set是如何实现动态的增加表的大小,hash_set是在hash_set.h中声明的,在hash_set.h中可以发现hash_set是继承_Hash类的,hash_set本身并没有太多的代码,只是对_Hash作了进一步的封装,这种做法在STL中非常常见,如stack栈queue单向队列都是以deque双向队列作底层数据结构再加一层封装。

_Hash类的定义和实现都在xhash.h类中,微软对_Hash类的第一句注释如下——

        hash table -- list with vector of iterators for quick access。

哈哈,这句话说的非常明白。这说明_Hash实际上就是由vector和list组成哈希表。再阅读下代码可以发现_Hash类增加空间由_Grow()函数完成,当空间不足时就倍增,并且表中原有数据都要重新计算hash值以确定新的位置。

知道了_Hash类是如何运作的,下面就来考虑如何实现强化版的hash_table。当然有二个地方还可以改进:

1._Hash类使用的list为双向链表,但在在哈希表中使用普通的单链表就可以了。因此使用STL中的vector再加入《STL系列之八 slist单链表》一文中的slist来实现强化版的hash_table。

2.在空间分配上使用了一个近似于倍增的素数表,最开始取第一个素数,当空间不足时就使用下一个素数。经过实际测试这种效果要比倍增法高效一些。

在这二个改进之上的强化版的hash_table代码如下:

[cpp] view plaincopyprint?
  1. //使用vector< slist<T> >为容器的hash_table   
  2. // by MoreWindows( http://blog.csdn.net/MoreWindows )  
  3. templateclass T, class container = vector<slist<T>> >  
  4. class hash_table  
  5. {  
  6. public:  
  7.     hash_table();  
  8.     hash_table(const int ntablesize);  
  9.     ~hash_table();  
  10.     void clear();  
  11.     bool insert(T &n);  
  12.     void insert(T *pFirst, T *pLast);  
  13.     bool erase(T &n);  
  14.     void resize(int nNewTableSize);  
  15.     bool find(T &n);  
  16.     int size();  
  17.     int HashFun(T &n);  
  18. private:  
  19.     static int findNextPrime(int curPrime);  
  20. public:  
  21.     int         m_nDataCount;  
  22.     int         m_nTableSize;  
  23.     container   m_Table;  
  24.     static const unsigned int m_primes[50];  
  25. };  
  26. //素数表   
  27. templateclass T, class container>  
  28. const unsigned int hash_table<T, container>::m_primes[50] = {  
  29.     53, 97, 193, 389, 769, 1453, 3079, 6151, 1289, 24593, 49157, 98317,   
  30.     196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917,   
  31.     25165843, 50331653, 100663319, 201326611, -1  
  32. };  
  33. templateclass T, class container>  
  34. int inline hash_table<T, container>::HashFun(T &n)  
  35. {  
  36.     return (n ^ 0xdeadbeef) % m_nTableSize;  
  37. }  
  38. templateclass T, class container>  
  39. hash_table<T, container>::hash_table()  
  40. {  
  41.     m_nDataCount = 0;  
  42.     m_nTableSize = m_primes[0];  
  43.     m_Table.resize(m_nTableSize);  
  44. }  
  45. templateclass T, class container>  
  46. hash_table<T, container>::hash_table(const int ntablesize)  
  47. {  
  48.     m_nDataCount = 0;  
  49.     m_nTableSize = ntablesize;  
  50.     m_Table.resize(m_nTableSize);  
  51. }  
  52. templateclass T, class container>  
  53. hash_table<T, container>::~hash_table()  
  54. {  
  55.     clear();  
  56. }  
  57. templateclass T, class container>  
  58. void hash_table<T, container>::clear()  
  59. {  
  60.     for (int i = 0; i < m_nTableSize; i++)  
  61.         m_Table[i].clear();  
  62.     m_nDataCount = 0;  
  63. }  
  64. templateclass T, class container>  
  65. bool hash_table<T, container>::insert(T &n)  
  66. {  
  67.     int key = HashFun(n);  
  68.     if (!m_Table[key].find(n))  
  69.     {  
  70.         m_nDataCount++;  
  71.         m_Table[key].push_front(n);  
  72.         if (m_nDataCount >= m_nTableSize)  
  73.             resize(findNextPrime(m_nTableSize));  
  74.     }  
  75.     return true;  
  76. }  
  77. templateclass T, class container>  
  78. bool hash_table<T, container>::erase(T &n)  
  79. {  
  80.     int key = HashFun(n);  
  81.     if (m_Table[key].remove(n))  
  82.     {  
  83.         m_nDataCount--;  
  84.         return true;  
  85.     }  
  86.     else  
  87.     {  
  88.         return false;  
  89.     }  
  90. }  
  91. templateclass T, class container>  
  92. void hash_table<T, container>::insert(T *pFirst, T *pLast)  
  93. {  
  94.     for (T *p = pFirst; p != pLast; p++)  
  95.         this->insert(*p);  
  96. }  
  97. templateclass T, class container>  
  98. void hash_table<T, container>::resize(int nNewTableSize)  
  99. {  
  100.     if (nNewTableSize <= m_nTableSize)  
  101.         return;  
  102.     int nOldTableSize = m_nTableSize;  
  103.     m_nTableSize = nNewTableSize;  
  104.     container tempTable(m_nTableSize); //创建一个更大的表  
  105.     for (int i = 0; i < nOldTableSize; i++)//将原表中数据重新插入到新表中  
  106.     {  
  107.         Node<T> *cur = m_Table[i].m_head;  
  108.         while (cur != NULL)  
  109.         {  
  110.             int key = HashFun(cur->val);  
  111.             Node<T> *pNext = cur->next;  
  112.             cur->next = tempTable[key].m_head;  
  113.             tempTable[key].m_head = cur;  
  114.             cur = pNext;  
  115.         }  
  116.         m_Table[i].m_head = NULL;  
  117.     }  
  118.     m_Table.swap(tempTable);  
  119. }  
  120. templateclass T, class container>  
  121. int hash_table<T, container>::size()  
  122. {  
  123.     return m_nDataCount;  
  124. }  
  125. templateclass T, class container>  
  126. bool hash_table<T, container>::find(T &n)  
  127. {  
  128.     int key = HashFun(n);  
  129.     return m_Table[key].find(n);  
  130. }  
  131. //在素数表中找到比当前数大的最小数   
  132. templateclass T, class container>  
  133. int hash_table<T, container>::findNextPrime(int curPrime)  
  134. {  
  135.     unsigned int *pStart = (unsigned int *)m_primes;  
  136.     while (*pStart <= curPrime)  
  137.         ++pStart;  
  138.     return *pStart;  
  139. }  

下面再对set、hash_set、强化版hash_table的性能测试:

测试结果一(数据量500万):

测试结果二(数据量1千万):

测试结果三(数据量1千万):

可以看出,由于强化版hash_table的哈希表在增加表空间大小时会花费额外的一些时间,所以插入数据的用时与STL提供的hash_set用时相差不多了。但查找还是比hash_set要快的一些。

四.结语

从简化版到强化版的hash_table,我们不仅知道了hash_set底层数据结构——哈希表的运作机制,还知道了如何实现大小动态变化的哈希表。达到了本文让读者了解hash_set快速高效的原因。当然本文所给hash_table距真正的hash_set还有不小的距离,有兴趣的读者可以进一步改进。

此外,本文所示范的哈希表也与最近流行的NoSql数据库颇有渊源, NoSql数据库也是通过Key-Value方式来访问数据的(访问数据的方式上非常类似哈希表),其查找效率与传统的数据库相比也正如本文中hast_set与set的比较。正因为NoSql数据库在基础数据结构上的天然优势,所以它完全可以支持海量数据的查询修改且对操作性能要求很高场合如微博等。

 

 

转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/7330323

 

STL系列之十 全排列(百度迅雷笔试题)

分类: STL 他山之石 14467人阅读 评论(56)收藏举报

全排列在笔试面试中很热门,因为它难度适中,既可以考察递归实现,又能进一步考察非递归的实现,便于区分出考生的水平。所以在百度和迅雷的校园招聘以及程序员和软件设计师的考试中都考到了,因此本文对全排列作下总结帮助大家更好的学习和理解。对本文有任何补充之处,欢迎大家指出。

首先来看看题目是如何要求的(百度迅雷校招笔试题)。

用C++写一个函数, 如 Foo(const char *str), 打印出 str 的全排列,
如 abc 的全排列: abc, acb, bca, dac, cab, cba

 

一.全排列的递归实现

为方便起见,用123来示例下。123的全排列有123、132、213、231、312、321这六种。首先考虑213和321这二个数是如何得出的。显然这二个都是123中的1与后面两数交换得到的。然后可以将123的第二个数和每三个数交换得到132。同理可以根据213和321来得231和312。因此可以知道——全排列就是从第一个数字起每个数分别与它后面的数字交换。找到这个规律后,递归的代码就很容易写出来了:

[cpp] view plaincopyprint?
  1. //全排列的递归实现   
  2. #include <stdio.h>   
  3. #include <string.h>   
  4. void Swap(char *a, char *b)  
  5. {  
  6.     char t = *a;  
  7.     *a = *b;  
  8.     *b = t;  
  9. }  
  10. //k表示当前选取到第几个数,m表示共有多少数.   
  11. void AllRange(char *pszStr, int k, int m)  
  12. {  
  13.     if (k == m)  
  14.     {  
  15.         static int s_i = 1;  
  16.         printf("  第%3d个排列\t%s\n", s_i++, pszStr);  
  17.     }  
  18.     else  
  19.     {  
  20.         for (int i = k; i <= m; i++) //第i个数分别与它后面的数字交换就能得到新的排列  
  21.         {  
  22.             Swap(pszStr + k, pszStr + i);  
  23.             AllRange(pszStr, k + 1, m);  
  24.             Swap(pszStr + k, pszStr + i);  
  25.         }  
  26.     }  
  27. }  
  28. void Foo(char *pszStr)  
  29. {  
  30.     AllRange(pszStr, 0, strlen(pszStr) - 1);  
  31. }  
  32. int main()  
  33. {  
  34.     printf("         全排列的递归实现\n");  
  35.     printf("  --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n\n");  
  36.     char szTextStr[] = "123";  
  37.     printf("%s的全排列如下:\n", szTextStr);  
  38.     Foo(szTextStr);  
  39.     return 0;  
  40. }  

运行结果如下:

注意这样的方法没有考虑到重复数字,如122将会输出:

这种输出绝对不符合要求,因此现在要想办法来去掉重复的数列。

 

二.去掉重复的全排列的递归实现

由于全排列就是从第一个数字起每个数分别与它后面的数字交换。我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这二个数就不交换了。如122,第一个数与后面交换得212、221。然后122中第二数就不用与第三个数交换了,但对212,它第二个数与第三个数是不相同的,交换之后得到221。与由122中第一个数与第三个数交换所得的221重复了。所以这个方法不行。

换种思维,对122,第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。再考虑212,它的第二个数与第三个数交换可以得到解决221。此时全排列生成完毕。

这样我们也得到了在全排列中去掉重复的规则——去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。下面给出完整代码:

[cpp] view plaincopyprint?
  1. //去重全排列的递归实现   
  2. #include <stdio.h>   
  3. #include <string.h>   
  4. void Swap(char *a, char *b)  
  5. {  
  6.     char t = *a;  
  7.     *a = *b;  
  8.     *b = t;  
  9. }  
  10. //在pszStr数组中,[nBegin,nEnd)中是否有数字与下标为nEnd的数字相等  
  11. bool IsSwap(char *pszStr, int nBegin, int nEnd)  
  12. {  
  13.     for (int i = nBegin; i < nEnd; i++)  
  14.         if (pszStr[i] == pszStr[nEnd])  
  15.             return false;  
  16.     return true;  
  17. }  
  18. //k表示当前选取到第几个数,m表示共有多少数.   
  19. void AllRange(char *pszStr, int k, int m)  
  20. {  
  21.     if (k == m)  
  22.     {  
  23.         static int s_i = 1;  
  24.         printf("  第%3d个排列\t%s\n", s_i++, pszStr);  
  25.     }  
  26.     else  
  27.     {  
  28.         for (int i = k; i <= m; i++) //第i个数分别与它后面的数字交换就能得到新的排列  
  29.         {  
  30.             if (IsSwap(pszStr, k, i))  
  31.             {  
  32.                 Swap(pszStr + k, pszStr + i);  
  33.                 AllRange(pszStr, k + 1, m);  
  34.                 Swap(pszStr + k, pszStr + i);  
  35.             }  
  36.         }  
  37.     }  
  38. }  
  39. void Foo(char *pszStr)  
  40. {  
  41.     AllRange(pszStr, 0, strlen(pszStr) - 1);  
  42. }  
  43. int main()  
  44. {  
  45.     printf("         去重全排列的递归实现\n");  
  46.     printf("  --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n\n");  
  47.     char szTextStr[] = "122";  
  48.     printf("%s的全排列如下:\n", szTextStr);  
  49.     Foo(szTextStr);  
  50.     return 0;  
  51. }  

运行结果如下:

 

OK,到现在我们已经能熟练写出递归的方法了,并且考虑了字符串中的重复数据可能引发的重复数列问题。那么如何使用非递归的方法来得到全排列了?

 

三.全排列的非递归实现

要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。如"1234"的下一个排列就是"1243"。只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。

如何计算字符串的下一个排列了?来考虑"926520"这个字符串,我们从后向前找第一双相邻的递增数字,"20"、"52"都是非递增的,"26 "即满足要求,称前一个数字2为替换数,替换数的下标称为替换点,再从后面找一个比替换数大的最小数(这个数必然存在),0、2都不行,5可以,将5和2交换得到"956220",然后再将替换点后的字符串"6220"颠倒即得到"950226"。

对于像"4321"这种已经是最“大”的排列,采用STL中的处理方法,将字符串整个颠倒得到最“小”的排列"1234"并返回false。

这样,只要一个循环再加上计算字符串下一个排列的函数就可以轻松的实现非递归的全排列算法。按上面思路并参考STL中的实现源码,不难写成一份质量较高的代码。值得注意的是在循环前要对字符串排序下,可以自己写快速排序的代码(请参阅《白话经典算法之六 快速排序 快速搞定》),也可以直接使用VC库中的快速排序函数(请参阅《使用VC库函数中的快速排序函数》)。下面列出完整代码:

[cpp] view plaincopyprint?
  1. //全排列的非递归实现   
  2. #include <stdio.h>   
  3. #include <stdlib.h>   
  4. #include <string.h>   
  5. void Swap(char *a, char *b)  
  6. {  
  7.     char t = *a;  
  8.     *a = *b;  
  9.     *b = t;  
  10. }  
  11. //反转区间   
  12. void Reverse(char *a, char *b)  
  13. {  
  14.     while (a < b)  
  15.         Swap(a++, b--);  
  16. }  
  17. //下一个排列   
  18. bool Next_permutation(char a[])  
  19. {  
  20.     char *pEnd = a + strlen(a);  
  21.     if (a == pEnd)  
  22.         return false;  
  23.     char *p, *q, *pFind;  
  24.     pEnd--;  
  25.     p = pEnd;  
  26.     while (p != a)  
  27.     {  
  28.         q = p;  
  29.         --p;  
  30.         if (*p < *q) //找降序的相邻2数,前一个数即替换数  
  31.         {  
  32.             //从后向前找比替换点大的第一个数   
  33.             pFind = pEnd;  
  34.             while (*pFind <= *p)  
  35.                 --pFind;  
  36.             //替换   
  37.             Swap(pFind, p);  
  38.             //替换点后的数全部反转   
  39.             Reverse(q, pEnd);  
  40.             return true;  
  41.         }  
  42.     }  
  43.     Reverse(p, pEnd);//如果没有下一个排列,全部反转后返回true  
  44.     return false;  
  45. }  
  46. int QsortCmp(const void *pa, const void *pb)  
  47. {  
  48.     return *(char*)pa - *(char*)pb;  
  49. }  
  50. int main()  
  51. {  
  52.     printf("         全排列的非递归实现\n");  
  53.     printf("  --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n\n");  
  54.     char szTextStr[] = "abc";  
  55.     printf("%s的全排列如下:\n", szTextStr);  
  56.     //加上排序   
  57.     qsort(szTextStr, strlen(szTextStr), sizeof(szTextStr[0]), QsortCmp);  
  58.     int i = 1;  
  59.     do{  
  60.         printf("第%3d个排列\t%s\n", i++, szTextStr);  
  61.     }while (Next_permutation(szTextStr));  
  62.     return 0;  
  63. }  

测试一下,结果如下所示:

将字符串改成"cba"会输出:

 

至此我们已经运用了递归与非递归的方法解决了全排列问题,总结一下就是:

1.全排列就是从第一个数字起每个数分别与它后面的数字交换。

2.去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。

3.全排列的非递归就是由后向前找替换数替换点,然后由后向前找第一个比替换数大的数与替换数交换,最后颠倒替换点后的所有数据。

 

转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/7370155

如果觉得本文对您有帮助,请点击‘顶’支持一下,您的支持是我写作最大的动力,谢谢。

原创粉丝点击