STL 中 sort 函数用法简介
STL 中 sort 函数用法简介
做 ACM 题的时候,排序是一种经常要用到的操作。如果每次都自己写个冒泡之类的O(n^2) 排序,不但程序容易超时,而且浪费宝贵的比赛时间,还很有可能写错。 STL 里面有个 sort 函数,可以直接对数组排序,复杂度为n*log2(n) 。
使用这个函数,需要包含头文件#include <algorithm>。
这个函数可以传两个参数或三个参数。第一个参数是要排序的区间首地址,第二个参数是区间尾地址的下一地址。也就是说,排序的区间是[a,b) 。简单来说,有一个数组 int a[100] ,要对从 a[0] 到 a[99] 的元素进行排序,只要写 sort(a,a+100) 就行了,默认的排序方式是升序。
拿我出的“ AC 的策略”这题来说,需要对数组 t 的第 0 到 len-1 的元素排序,就写 sort(t,t+len);
对向量 v 排序也差不多, sort(v.begin(),v.end());
排序的数据类型不局限于整数,只要是定义了小于运算的类型都可以,比如字符串类string 。
如果是没有定义小于运算的数据类型,或者想改变排序的顺序,就要用到第三参数——比较函数。比较函数是一个自己定义的函数,返回值是 bool 型,它规定了什么样的关系才是“小于”。想把刚才的整数数组按降序排列,可以先定义一个比较函数 cmp
bool cmp(int a,int b) { return a>b; }
排序的时候就写 sort(a,a+100,cmp);
假设自己定义了一个结构体 node
struct node{ int a; int b; double c; }
有一个 node 类型的数组 nodearr[100] ,想对它进行排序:先按 a 值升序排列,如果 a 值相同,再按 b 值降序排列,如果 b 还相同,就按 c 降序排列。就可以写这样一个比较函数:
以下是代码片段:
bool cmp(node x,node y) { if(x.a!=y.a) return x.a<y.a; if(x.b!=y.b) return x.b>y.b; return return x.c>y.c; }
排序时写 sort(arr,a+100,cmp);
最后看一个完整的实例,初赛时的一道题目“文件名排序 ”。
以下是代码片段:
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
// 定义一个结构体来表示文件,a 代表文件名, b 代表文件类型(要么 "File" 要么 "Dir" )
struct node{
string a,b;
};
//ASCII 码中,所有大写字母排在所有小写字母前面,'A'<'Z'<'a'<'z'
// 而这题要求忽略大小写,所以不能直接用字符串的比较。自定义了一个lt 函数,就是 less than 的意思
// 先把两个字符串全部转化为小写,再比较大小(字典序)
bool lt(string x,string y) { int i; for(i=0;i<x.length();i++) if(x[i]>='A'&&x[i]<='Z') x[i]='a'+(x[i]-'A'); for(i=0;i<y.length();i++) if(y[i]>='A'&&y[i]<='Z') y[i]='a'+(y[i]-'A'); return x<y; }
// 自定义的比较函数,先按 b 值升序排列(也就是 "Dir" 排在 "File" 前面)
// 如果 b 值相同,再按 a 升序排列,用的是刚才定义的lt 函数
bool comp(node x,node y) { if(x.b!=y.b)return x.b<y.b; return lt(x.a,y.a); } int main() { node arr[10001]; int size=0; while(cin>>arr[size].a>>arr[size].b) size++; sort(arr,arr+size,comp); for(int i=0;i<size;i++) cout<<arr[i].a<<""<<arr[i].b<<endl; return 0; }
七种 qsort 排序方法
< 本文中排序都是采用的从小到大排序>
一、对 int 类型数组排序
int num[100]; Sample: int cmp ( const void *a , const void *b) { return *(int *)a - *(int *)b; } qsort(num,100,sizeof(num[0]),cmp);
二、对 char 类型数组排序(同 int 类型)
char word[100]; Sample: int cmp( const void *a , const void *b ) { return *(char *)a - *(char *)b; } qsort(word,100,sizeof(word[0]),cmp);
三、对 double 类型数组排序(特别要注意)
double in[100]; int cmp( const void *a , const void *b ) { return *(double *)a > *(double *)b ?1 : -1; } qsort(in,100,sizeof(in[0]),cmp) ;
四、对结构体一级排序
struct In { double data; int other; }s[100] // 按照 data 的值从小到大将结构体排序 , 关于结构体内的排序关键数据data 的类型可以很多种,参考上面的例子写 int cmp( const void *a ,const void *B) { return (*(In *)a)->data > (*(In*)B)->data ? 1 : -1; } qsort(s,100,sizeof(s[0]),cmp);
五、对结构体二级排序
struct In { int x; int y; }s[100]; // 按照 x 从小到大排序,当 x 相等时按照 y 从大到小排序 int cmp( const void *a , const void *b ) { struct In *c = (In *)a; struct In *d = (In *)b; if(c->x != d->x) return c->x -d->x; else return d->y - c->y; } qsort(s,100,sizeof(s[0]),cmp);
六、对字符串进行排序
struct In { int data; char str[100]; }s[100];
// 按照结构体中字符串 str 的字典顺序排序
int cmp ( const void *a , const void *b) { return strcmp( (*(In *)a)->str ,(*(In *)B)->str ); } qsort(s,100,sizeof(s[0]),cmp);
七、计算几何中求凸包的 cmp
int cmp(const void *a,const void *B) // 重点 cmp 函数,把除了 1 点外的所有点,旋转角度排序 { struct point *c=(point *)a; struct point *d=(point *)b; if( calc(*c,*d,p[1]) < 0) return 1; else if( !calc(*c,*d,p[1]) &&dis(c->x,c->y,p[1].x,p[1].y) < dis(d->x,d->y,p[1].x,p[1].y)) // 如果在一条直线上,则把远的放在前面 return 1; else return -1; }
N!
斯特灵公式是一条用来取n阶乘近似值的数学公式。一般来说,当n很大的时候,n阶乘的计算量十分大,所以斯特灵公式十分好用,而且,即使在
n很小的时候,斯特灵公式的取值已经十分准确。
公式为:
这就是说,对于足够大的整数n,这两个数互为近似值。更加精确地:
或者:
求N!的位数:=log10(N!)+1=0.5*log10(2*PI*n)+n*log10(n/e);
STL中list的使用:
STL中的list就是一双向链表,可高效地进行插入删除元素。现总结一下它的操作。
文中所用到两个list对象c1,c2分别有元素c1(10,20,30) c2(40,50,60)。还有一个list<int>::iteratorciter用来指向c1或c2元素。
list对象的声明构造():
A.list<int>c0; //空链表
B.list<int>c1(3); //建一个含三个默认值是0的元素的链表
C.list<int>c2(5,2); //建一个含五个元素的链表,值都是2
D.list<int>c4(c2); //建一个c2的copy链表
E.list<int>c5(c1.begin(),c1.end());
//c5含c1一个区域的元素[_First,_Last)。
1.assign()分配值,有两个重载:
c1.assign(++c2.begin(), c2.end()) //c1现在为(50,60)。
c1.assing(7,4) //c1中现在为7个4,c1(4,4,4,4,4,4,4)。
2.back()返回最后一元素的引用:
int i=c1.back(); //i=30
const int i=c2.back(); //i=60且不可修改
3.begin()返回第一个元素的指针(iterator)
citer=c1.begin(); // *citer=10
list<int>::const_iteratorcciter=c1.begin(); //*cciter=10且为const。
4.clear()删除所有元素
c1.clear(); //c1为空 c1.size为0;
5.empty()判断是否链表为空
bool B=c1.empty(); //若c1为空B=true;否则B=false;
6.end()返回最后一个元素的下一位置的指针(list为空时end()=begin())
citer=c1.end(); //*(--citer)=30;
同begin()返回一个常指针,不能修改其中元素。
7.erase()删除一个元素或一个区域的元素(两个重载)
c1.erase(c1.begin()); // c1现为(20,30);
c1.erase(++c1.begin(),c1.end()); // c1现为(10);
8.front()返回第一个元素的引用:
int i=c1.front(); //i=10;
const int i=c1.front(); //i=10且不可修改。
9.insert()在指定位置插入一个或多个元素(三个重载):
c1.insert(++c1.begin(),100);//c1(10,100,20,30)
c1.insert(c1.begin(),2,200);//c1(200,200,20,30);
c1.insert(++c1.begin(),c2.begin(),--c2.end());
//c1(10,40,50,20,30);
10.max_size()返回链表最大可能长度(size_type就是int型):
list<int>::size_typei=c1.max_size(); //i=1073741823
11.merge()合并两个链表并使之默认升序(也可改):
c2.merge(c1); //c1现为空;c2现为c2(10,20,30,40,50,60)
c2.merge(c1,greater<int>()); //同上,但c2现为降序
12.pop_back()删除链表尾的一个元素
c1.pop_back() //c1(10,20);
13.pop_front()删除链表头的一元素
c1.pop_front() //c1(20,30)
14.push_back()增加一元素到链表尾
c1.push_back(100) //c1(10,20,30,100)
15.push_front()增加一元素到链表头
c1.push_front(100) //c1(100,10,20,30)
16.rbegin()返回链表最后一元素的后向指针(reverse_iterator or const)
list<int>::reverse_iteratorriter=c1.rbegin(); //*riter=30
17.rend()返回链表第一元素的下一位置的后向指针
list<int>::reverse_iteratorriter=c1.rend(); // *(--riter)=10
18.remove()删除链表中匹配值的元素(匹配元素全部删除)
c1.remove(10); //c1(20,30)
19.remove_if()删除条件满足的元素(会遍历一遍链表)
c1.remove_if( is_odd<int> () );//c1(10,20,30)
//is_odd自己写(表奇数)
20.resize()重新定义链表长度(两重载):
c1.resize(4) //c1(10,20,30,0)用默认值填补
c1.resize(4,100) //c1(10,20,30,100)用指定值填补
21.reverse()反转链表:
c1.reverse(); //c1(30,20,10)
22.size()返回链表中元素个数
list<int>::size_type i=c1.size();//i=3
23.sort()对链表排序,默认升序(可自定义)
c1.sort(); //c1(10,20,30)
c1.sort(great<int>());//c1(30,20,10)
24.splice()对两个链表进行结合(三个重载)
c1.splice(++c1.begin(),c2);
//c1(10,40,50,60,20,30) c2为空 全合并
c1.splice(++c1.begin(),c2,++c2.begin());
//c1(10,50,20,30) ; c2(40,60) 指定元素合并
c1.splice(++c1.begin(),c2,++c2.begin(),c2.end());
//c1(10,50,60,20,30); c2(40) 指定范围合并
25.swap()交换两个链表(两个重载)
c1.swap(c2); //c1(40,50,60);
swap(c1,c2); //c1(40,50,60)
26.unique()删除相邻重复元素(断言已经排序,因为它不会删除不相邻的相同元素)
c1.unique();
//假设c1开始(-10,10,10,20,20,-10)则之后为c1(-10,10,20,-10)
c1.unique(mypred); //自定义谓词
注意:insert函数是右结合方式
。insert(j,*(--j))是最右向左进行的
AVL 树
2011-03-19 20:30
AVL树又称为高度平衡的二叉搜索树,是1962年由两位俄罗斯数学家G.M.Adel’ son-Vel'sky和E.M.Landis提出的。引人它的目的,是为了提高二叉搜索树的效率,减 少树的平均搜索长度。为此,就必须向二又搜索树每插人一个新结点时调整树的结构,使 得二又搜索树保持平衡,从而尽可能降低树的高度,减少树的平均搜索长度。 一、AVL树的定义:一棵ALV树或者是空树,或者是具有下列性质的二叉搜索树:它的左子树和右子树 都是AVL树,且左子树和右子树的高度之差的绝对值不超过1。 图(a)给出的二叉搜索树不是AVL树,根的右子树的高度为l,而左子树的高度 为4。图(b)给出的二叉搜索树是AVL树。在图中每个结点旁边所注的数字结出 该结点右子树的高度减去左子树的高度所得的高度差。称这个数字为结点的平衡因子(balance factor)。 根据 AVL树的定义,任一结点的平衡因子只能取一1,0和 1。如果一个结点的平衡因子的绝对值大于1,则这棵二叉搜索树就失去了平衡,不再是AVL树了。 如果一棵二叉搜索树是高度平衡的,它就成为AVL树,如果它有N个结点,其高度可保持在O(log2n),平均搜索长度也可保持在O(log2n)。 二、平衡化旋转 如果在一棵原本是平衡的二又搜索树中插入一个新结点,造成了不平衡。此时必须 调整树的结构,使之平衡化。平衡化旋转有两类:单旋转(左旋和右旋)和双旋转(左平衡 和右平衡)。给出加人了平衡化旋转操作的AVL一树的类声明如下。 template <class Type> class AVLTree{ //平衡的一叉搜索树(AVL)类定义 public: struct AVLNode { //AVL树结点的类定义 Type data; AVLNode<Type>*left,* right; int balance ; AVLNode():left(NULL),right(NULL), balance(0){} AVLNode(Type d,AVLNode<Type>* l= NULL,AVLNode<Type>* r= NULL): data(d),left(l),right(r),balance(0){} }; protected: Type RefValue; //插人结束的标志 AVLNode<TyPe>* root ; //根结点的指针 int Insert( AVLNOde<Type>*&tree, Type x);//插人 void RotateLeft(AVLN0de<Type*>*Tree,AVLNOde<Type>*&NewTree); //左单旋转 void RotateRight( AVLNode<Type>*Tree, AVLNode<Type>*& NewTree); //右单旋转 void LeftBalance(AVLNode<Type>*&Tree); //左平衡化 void RightBalance( AVLNode<Type>*& Tree); //右平衡化 int Heisht( AVLNode<TyPe>* t) const; //求高度 public: AVLTree():fOOt( NULL){ } //构造函数:构造一棵空 AVL树 AVLNode(Type Ref): RefValue(Ref),root(NULL){} //构造函数:构造非空 AVL树 int Insert(Type){return Insert(root,x);} int Height() Const; }
1.左单旋转(rotate left) 图7.18 左单旋转前后树的变化 沿插人路径检查三个结点A,C和E。它们处于一条方向为“\”的直线上,需要做左 单旋转:以结点C为旋转轴,让结点A反时针旋转成为C的左子女,C代替原来A的位 置,原来C的左子女D转为A的右子女,旋转后的形状如图7.18(C)所示。通过检查各 个结点的平衡因子可知,树又恢复了平衡。 图19 右单旋转前后树的变化 3.先左后古双旋转(rotation left right) 图20 先左后右双旋转 图21 先右后左双旋转 三、AVL树的插入和删除 在向一棵本来是高度平衡的AVL树中插人一个新结点时,应判断从插入结点到根 的路径L各结点的平衡因子的变化。如果树中某个结点的平衡因子的绝对值 |balance| >1,则出现了不平衡,需要从离插人结点最近的发生不平衡的结点沿插人路径做平衡化处理。
(2)将结点x从树中删去。因为结点X最多有一个子女,可以简单地把X的双亲? 点中原来指向x的指针改指到这个子女结点;如果结点X没有子女,x双亲结点的相应指 针置为NULL。然后将原来以结点工为根的子树的高度减1,并沿X通向根的路径反向 追踪高度的这一变化对路径上各个结点P的影响,进行平衡化处理。 case 3c:如果 p与q的平衡因子相反,则执行一个双旋转来恢复平衡,先围绕q转再 围绕产转。新的根结点的平衡因子置为0,其他结点的平衡因子相应处理。参看图7.23 ·中的 case 3c。 四、AVL的高度 若设在新结点插人前AVL树的高度为h,结点个数为n,则插人一个新结点的时间 是O(h)。这与一般的二叉搜索树相同。但对于一棵不一定平衡的三叉搜索树来说,树的 高度最大可能是h=n-1,因此,最坏情况下,在一般二又搜索树中插入一个新结点所需 的时间为o(n)。那么,对于AVL树来说,h应当是多大呢? 二叉搜索树适合于组织在内存中的较小的索引(或目录)。对于存放在外存中的较大 的文件系统,用二叉搜索树来组织索引就不太合适了。若以结点为内外存交换的单位,则在搜索过程中为找到所需的关键码,需对外存进行log2n次访问,这是很费时的。因此在文件检索系统中大量使用的是用B树或B十树做文件索引。 |
堆排序
1、堆排序定义
n个关键字序列Kl,K2,…,Kn称为堆,当且仅当该序列满足如下性质(简称为堆性质):
(1) ki≤K2i且ki≤K2i+1或(2)Ki≥K2i且ki≥K2i+1(1≤i≤)
若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。
【例】关键字序列(10,15,56,25,30,70)和(70,56,30,25,15,10)分别满足堆性质(1)和(2),故它们均是堆,其对应的完全二叉树分别如小根堆示例和大根堆示例所示。
2、大根堆和小根堆
根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆。
根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆。
注意:
①堆中任一子树亦是堆。
②以上讨论的堆实际上是二叉堆(BinaryHeap),类似地可定义k叉堆。
3、堆排序特点
堆排序(HeapSort)是一树形选择排序。
堆排序的特点是:在排序过程中,将R[l..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系【参见二叉树的顺序存储结构】,在当前无序区中选择关键字最大(或最小)的记录。
4、堆排序与直接插入排序的区别
直接选择排序中,为了从R[1..n]中选出关键字最小的记录,必须进行n-1次比较,然后在R[2..n]中选出关键字最小的记录,又需要做n-2次比较。事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保留这些比较结果,所以后一趟排序时又重复执行了这些比较操作。
堆排序可通过树形结构保存部分比较结果,可减少比较次数。
5、堆排序
堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。
(1)用大根堆排序的基本思想
①先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
②再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。
……
直到无序区只有一个元素为止。
(2)大根堆排序算法的基本操作:
①初始化操作:将R[1..n]构造为初始堆;
②每一趟排序的基本操作:将当前无序区的堆顶记录R[1]和该区间的最后一个记录交换,然后将新的无序区调整为堆(亦称重建堆)。
注意:
①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。
②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。堆排序和直接选择排序相反:在任何时刻,堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止。
(3)堆排序的算法:
void HeapSort(SeqIAst R) { //对R[1..n]进行堆排序,不妨用R[0]做暂存单元 int i; BuildHeap(R); //将R[1-n]建成初始堆 for(i=n;i>1;i--){ //对当前无序区R[1..i]进行堆排序,共做n-1趟。 R[0]=R[1];R[1]=R[i];R[i]=R[0]; //将堆顶和堆中最后一个记录交换 Heapify(R,1,i-1); //将R[1..i-1]重新调整为堆,仅有R[1]可能违反堆性质 } //endfor } //HeapSort
(4) BuildHeap和Heapify函数的实现
因为构造初始堆必须使用到调整堆的操作,先讨论Heapify的实现。
① Heapify函数思想方法
每趟排序开始前R[l..i]是以R[1]为根的堆,在R[1]与R[i]交换后,新的无序区R[1..i-1]中只有R[1]的值发生了变化,故除R[1]可能违反堆性质外,其余任何结点为根的子树均是堆。因此,当被调整区间是R[low..high]时,只须调整以R[low]为根的树即可。
"筛选法"调整堆
R[low]的左、右子树(若存在)均已是堆,这两棵子树的根R[2low]和R[2low+1]分别是各自子树中关键字最大的结点。若R[low].key不小于这两个孩子结点的关键字,则R[low]未违反堆性质,以R[low]为根的树已是堆,无须调整;否则必须将R[low]和它的两个孩子结点中关键字较大者进行交换,即R[low]与R[large](R[large].key=max(R[2low].key,R[2low+1].key))交换。交换后又可能使结点R[large]违反堆性质,同样由于该结点的两棵子树(若存在)仍然是堆,故可重复上述的调整过程,对以R[large]为根的树进行调整。此过程直至当前被调整的结点已满足堆性质,或者该结点已是叶子为止。上述过程就象过筛子一样,把较小的关键字逐层筛下去,而将较大的关键字逐层选上来。因此,有人将此方法称为"筛选法"。
具体的算法【参见教材】
②BuildHeap的实现
要将初始文件R[l..n]调整为一个大根堆,就必须将它所对应的完全二叉树中以每一结点为根的子树都调整为堆。
显然只有一个结点的树是堆,而在完全二叉树中,所有序号 的结点都是叶子,因此以这些结点为根的子树均已是堆。这样,我们只需依次将以序号为 ,-1,…,1的结点作为根的子树都调整为堆即可。
具体算法【参见教材】。
5、大根堆排序实例
对于关键字序列(42,13,24,91,23,16,05,88),在建堆过程中完全二叉树及其存储结构的变化情况参见【动画演示】。
6、算法分析
堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,它们均是通过调用Heapify实现的。
堆排序的最坏时间复杂度为O(nlgn)。堆排序的平均性能较接近于最坏性能。
由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
堆排序是就地排序,辅助空间为O(1),
它是不稳定的排序方法。
C++ 折半查找(二分查找)算法
/*二分查找(折半查找)算法 #include <iostream> using namespace std; int Bisearch(int data[],int x,int begin,int end) {//折半查找算法实现 if (begin>end) {//判断是不是只有 一个元素可以比较 return -1; exit(0); } int mid = (begin+end)/2; if (x==data[mid]) {//如果X的值与data[mid]的值相等,则查找成功,本函数执行完毕 //cout<<"The mid:"<<mid<<endl; return mid; exit(0); } else if (x<data[mid]) {//当X <data[mid] 时,从左面 开始查找数据X ,用递归实现 return Bisearch(data,x,begin,mid-1); } else {//如果x<data[mid]的情况下,即往数组的右半部分扫描数组 return Bisearch(data,x,mid+1,end); } } int main() { const int length = 10; int data[length]; for (int i=0;i<length;++i) { data[i] = i; } cout<<"数组元素有:"<<endl; for (int i=0;i!=length;++i) { cout<<data[i]<<" "; } cout<<endl; int x; cout<<"输入要查找的元素:"<<endl; cin>>x; int location=Bisearch(data,x,0,9); //cout<<"The location:"<<location<<endl; if(location==-1) { cout<<"Error! 未找到元素!"<<endl; } else { cout<<"要查找的元素: "<<x<<" 的位置在第 "<<location+1<<"个位置!"<<endl; } } |
hash表,有时候也被称为散列表。个人认为,hash表是介于链表和二叉树之间的一种中间结构。链表使用十分方便,但是数据查找十分麻烦;二叉树中的数据严格有序,但是这是以多一个指针作为代价的结果。hash表既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。
打个比方来说,所有的数据就好像许许多多的书本。如果这些书本是一本一本堆起来的,就好像链表或者线性表一样,整个数据会显得非常的无序和凌乱,在你找到自己需要的书之前,你要经历许多的查询过程;而如果你对所有的书本进行编号,并且把这些书本按次序进行排列的话,那么如果你要寻找的书本编号是n,那么经过二分查找,你很快就会找到自己需要的书本;但是如果你每一个种类的书本都不是很多,那么你就可以对这些书本进行归类,哪些是文学类,哪些是艺术类,哪些是工科的,哪些是理科的,你只要对这些书本进行简单的归类,那么寻找一本书也会变得非常简单,比如说如果你要找的书是计算机方面的书,那么你就会到工科一类当中去寻找,这样查找起来也会显得麻烦。
不知道这样举例你清楚了没有,上面提到的归类方法其实就是hash表的本质。下面我们可以写一个简单的hash操作代码。
a)定义hash表和基本数据节点
1. typedef struct _NODE 2. { 3. int data; 4. struct _NODE* next; 5. }NODE; 6. 7. typedef struct _HASH_TABLE 8. { 9. NODE* value[10]; 10. }HASH_TABLE;typedef struct _NODE
{
int data;
struct _NODE* next;
}NODE;
typedef struct _HASH_TABLE
{
NODE* value[10];
}HASH_TABLE;
b)创建hash表
1. HASH_TABLE* create_hash_table() 2. { 3. HASH_TABLE* pHashTbl = (HASH_TABLE*)malloc(sizeof(HASH_TABLE)); 4. memset(pHashTbl, 0, sizeof(HASH_TABLE)); 5. return pHashTbl; 6. }
HASH_TABLE* create_hash_table()
{
HASH_TABLE* pHashTbl = (HASH_TABLE*)malloc(sizeof(HASH_TABLE));
memset(pHashTbl, 0, sizeof(HASH_TABLE));
return pHashTbl;
}
c)在hash表当中寻找数据
1. NODE* find_data_in_hash(HASH_TABLE* pHashTbl, int data) 2. { 3. NODE* pNode; 4. if(NULL == pHashTbl) 5. return NULL; 6. 7. if(NULL == (pNode = pHashTbl->value[data % 10])) 8. return NULL; 9. 10. while(pNode){ 11. if(data == pNode->data) 12. return pNode; 13. pNode = pNode->next; 14. } 15. return NULL; 16. }NODE* find_data_in_hash(HASH_TABLE* pHashTbl, int data)
{
NODE* pNode;
if(NULL == pHashTbl)
return NULL;
if(NULL == (pNode = pHashTbl->value[data % 10]))
return NULL;
while(pNode){
if(data == pNode->data)
return pNode;
pNode = pNode->next;
}
return NULL;
}
d)在hash表当中插入数据
1. STATUS insert_data_into_hash(HASH_TABLE* pHashTbl, int data) 2. { 3. NODE* pNode; 4. if(NULL == pHashTbl) 5. return FALSE; 6. 7. if(NULL == pHashTbl->value[data % 10]){ 8. pNode = (NODE*)malloc(sizeof(NODE)); 9. memset(pNode, 0, sizeof(NODE)); 10. pNode->data = data; 11. pHashTbl->value[data % 10] = pNode; 12. return TRUE; 13. } 14. 15. if(NULL != find_data_in_hash(pHashTbl, data)) 16. return FALSE; 17. 18. pNode = pHashTbl->value[data % 10]; 19. while(NULL != pNode->next) 20. pNode = pNode->next; 21. 22. pNode->next = (NODE*)malloc(sizeof(NODE)); 23. memset(pNode->next, 0, sizeof(NODE)); 24. pNode->next->data = data; 25. return TRUE; 26. }STATUS insert_data_into_hash(HASH_TABLE* pHashTbl, int data)
{
NODE* pNode;
if(NULL == pHashTbl)
return FALSE;
if(NULL == pHashTbl->value[data % 10]){
pNode = (NODE*)malloc(sizeof(NODE));
memset(pNode, 0, sizeof(NODE));
pNode->data = data;
pHashTbl->value[data % 10] = pNode;
return TRUE;
}
if(NULL != find_data_in_hash(pHashTbl, data))
return FALSE;
pNode = pHashTbl->value[data % 10];
while(NULL != pNode->next)
pNode = pNode->next;
pNode->next = (NODE*)malloc(sizeof(NODE));
memset(pNode->next, 0, sizeof(NODE));
pNode->next->data = data;
return TRUE;
}
e)从hash表中删除数据
1. STATUS delete_data_from_hash(HASH_TABLE* pHashTbl, int data) 2. { 3. NODE* pHead; 4. NODE* pNode; 5. if(NULL == pHashTbl || NULL == pHashTbl->value[data % 10]) 6. return FALSE; 7. 8. if(NULL == (pNode = find_data_in_hash(pHashTbl, data))) 9. return FALSE; 10. 11. if(pNode == pHashTbl->value[data % 10]){ 12. pHashTbl->value[data % 10] = pNode->next; 13. goto final; 14. } 15. 16. pHead = pHashTbl->value[data % 10]; 17. while(pNode != pHead ->next) 18. pHead = pHead->next; 19. pHead->next = pNode->next; 20. 21. final: 22. free(pNode); 23. return TRUE; 24. }