数据结构之第七章学习小结
感觉查找这一章学的不深,下面做一些简单小结吧。
1、查找表:是由同一类型的数据元素(或记录)构成的集合。
2、关键字:是数据元素(或记录)中某个数据项的值,用它可以标识一个数据元素(或记录)。
主关键字:若此关键字可以唯一地标识一个记录,则称此关键字为主关键字。
反之称为次关键字。
3、查找
(1)静态查找表:在查找的同时不对表进行修改操作。
(2)动态查找表:在查找的同时对表做修改操作(如插入、删除)。
🔺4、平均查找长度:
为确定记录在查找表中的位置,需和给定值进行比较的关键字个数的期望值,称为查找算法的在查找成功时的平均查找长度。(ASL)
ASL = ∑ PiCi(从i = 1 到 i = n求和)
其中,Pi为查找表中第i个记录的概率,且∑ P = 1;
Ci为找到表中其关键字与给定值相等的第i个记录时,和给定值已进行过比较的关键字个数。(显然,Ci随查找过程的不同而不同)。
查找:
(1)首先是顺序查找
其数据类型的定义如下:
1 typedef KeyType int;//这个根据具体情况去定义;在这里定义为int; 2 3 typedef struct{ 4 KeyType key; 5 InfoType otherinfo;//这个根据具体情况去改,这里只是抽象的说成还要添加这些类型。 6 }ElemType; 7 8 typedef struct{ 9 ElemType *R; 10 int length; 11 }SSTable;
1)普通的顺序查找
1 int Search_Seq(SSTable ST,KeyType key) 2 {//在顺序表ST中顺序查找其关键字等于Key的数据元素。若找到,则函数值为该元素在表中的位置,否则为0; 3 for(int i = ST.length ; i >= 1 ;i--) 4 { 5 if(ST.R[i].key==key) 6 return i;//从后往前找; 7 return 0; 8 } 9 10 }
2)设置监视哨的顺序查找
1 int Search_Seq(SSTable ST,KeyType key) 2 {//在顺序表ST中顺序查找其关键字等于key的数据元素。若找到,则函数值为该元素在表中的位置,否则为0; 3 ST.R[0].key = key; 4 for(int i = ST.length;ST.R[i].key!=key;--i); 5 return i ; 6 7 8 }
(2)折半查找(二分查找)
算法描述:
1 int Search_Bin(SSTable ST,KeyType key) 2 {//在有序表ST表中折半查找其关键字等于key的数据元素。若找到,则函数值为该元素在表中的位置,否则为0; 3 int low = 1 ,high = ST.length;//置查找区间初值。 4 int mid; 5 while(low<=high) 6 { 7 mid = (low+high)/2; 8 if(key==ST.R[mid].key) //找到待查找元素; 9 return mid; 10 else 11 if(key<ST.R[mid].key) high = mid - 1;//继续在前一子表进行查找; 12 else 13 low = mid + 1;//继续在后一子表中进行查找; 14 } 15 return 0; //表中不存在待查元素; 16 17 }
以上是非递归结构(即迭代)实际上二分也可以用递归结构。
链式结构也可以进行二分查找,但是不能在对数时间内完成。因为实际上,每次比较的话,链表都得遍历一遍,花了O(n)时间。
二分查找应用场景的局限性:
(1)基于顺序表的存储结构;
(2)针对有序数据;
(3)数据量小且比较操作不耗时时,不需要二分;
(4)数据量太大也不行(超出内存可用连续空间);
(3)分块查找;
针对前面查找的局限性,我们才有了下面的排序(在树表中进行查找);
(4)二叉排序树;
定义:二叉排序树或者是一颗空树或者是具有下列性质的二叉树;
1)若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值。
2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。
3)它的左、右子树也分别为二叉排序树。
二叉排序树的二叉链表存储表示
1 typedef struct{ 2 KeyType key;//关键字项; 3 InfoType otherinfo; //其他数据项; 4 }ElemType; 5 typedef struct BSTNode{ 6 ElemType data;//每个结点的数据域包括关键字项和其他数据项 7 struct BSTNode *lchild,*rchild;//左右孩子的指针; 8 }BSTNode,*BSTree;
二叉排序树的查找
1 BSTNode* Search(BSTree t ,KeyType x) 2 { 3 if(t==NULL)return NULL; 4 if(t->data.key==x) return t; 5 if(t->data.key>x) return Search(t->lchild,x); 6 if(t->data.key<x) return Search(t->rchild,x); 7 }
二叉排序树的插入
1 void InsertBST(BSTree &T,ElemType e) 2 {//当二叉排序树T中不存在关键字等于e.key的数据元素时,则插入该元素; 3 if(T==NULL) 4 { 5 s = new BSTNode; 6 s->data = e; 7 s->lchild = s->rchild = NULL; 8 T = s; 9 }else 10 if(e.key<T->data.key) 11 InsertBST(T->lchild,e); 12 else if(e.key>T->data.key) 13 InsertBST(T->rchild,e); 14 15 }
二叉排序树的创建
1 void CreatBST(BSTree &T) 2 {//依次读入一个关键字为key的结点,将此结点插入二叉排序树T中; 3 T = NULL;//将二叉排序树T初始化为空树; 4 cin>>e; 5 while(e.key!=ENDFLAG)//ENDFLAG为自定义变量,作为输入结束的标志; 6 { 7 InsertBST(T,e); 8 cin>>e; 9 } 10 }
实际上,二叉排序树的查找复杂度是介于O(log2n)~O(n)之间的,那么如何提高二叉排序树的查找效率呢?
就是尽量让二叉排序树的形状均衡;
所以接着引入了平衡树二叉树。
平衡树(AVL树)具有如下特征:
(1)左子树和右子树的深度之差的绝对值不超过1;
(2)左子树和右子树也是平衡二叉树;
对于平衡树我是不太熟悉的,只知道基本概念;但是它的查找和修改基本是以对数时间的,解决了二分查找中只能进行静态查找的局限;
接着又讲了B-树和B+树,B-树解决了数据量大时的查找,而B+树则在B-树的基础上解决了区间查找问题。
对于B-树和B+树我也不太熟悉,在这里便不展开了。
散列表的查找
散列查找法主要研究一下两方面的问题:
(1)如何构造散列函数;
(2)如何处理冲突;
1)造表时如何解决; 2)查找时如何解决;
散列表的构造方法:
1)数字分析法; 2)平方取中法 ;3)折叠法; 4)除留余数法;
🔺处理冲突的方法:
1、开放地址法 (1)线性探测法 ; (2)二次探测法;(3)伪随机探测法;
2、链地址法(实际上就是邻接表);
这一章的作业题是:利用哈希表进行二次探测法处理冲突;
The task of this problem is simple: insert a sequence of distinct positive integers into a hash table, and output the positions of the input numbers. The hash function is defined to be H(key)=key%TSize where TSize is the maximum size of the hash table. Quadratic probing (with positive increments only) is used to solve the collisions.
Note that the table size is better to be prime. If the maximum size given by the user is not prime, you must re-define the table size to be the smallest prime number which is larger than the size given by the user.
Input Specification:
Each input file contains one test case. For each case, the first line contains two positive numbers: MSize (≤104) and N (≤MSize) which are the user-defined table size and the number of input numbers, respectively. Then N distinct positive integers are given in the next line. All the numbers in a line are separated by a space.
Output Specification:
For each test case, print the corresponding positions (index starts from 0) of the input numbers in one line. All the numbers in a line are separated by a space, and there must be no extra space at the end of the line. In case it is impossible to insert the number, print "-" instead.
Sample Input:
4 4
10 6 4 15
Sample Output:
0 1 4 -
解这道题要知道二次探测法的公式: hi=(h(key)+i*i)%m 0≤i≤m-1 //即di=i2
即探查序列为d=h(key),d+12,d+22,…,等。
代码如下:
1 #include<iostream> 2 #include<stdio.h> 3 #include<cmath> 4 using namespace std; 5 6 const int maxn = 1e4+5; 7 int mod , n; //定义题目给的模数和数的个数; 8 int a[maxn]; 9 bool isprime(int tmp) //判断是否为素数; 10 { 11 int flag = 0; 12 int tp = sqrt(tmp); //取根号一半去判断就好了; 13 if(tmp==1) //1不是素数,这里单独判断; 14 return false; 15 else 16 { 17 for(int i = 2 ; i <= tp ;i++) 18 { 19 if(tmp%i==0) //如果能被除1外的数整除,则不是素数; 20 { 21 flag = 1; 22 break; 23 } 24 } 25 if(flag==1) 26 { 27 return false; 28 } 29 return true; 30 31 } 32 33 } 34 bool vis[maxn];//用于判断哪些位置被占用了; 35 int fl = 0; 36 int main() 37 { 38 scanf("%d%d",&mod,&n); 39 for(int i = 0 ; i < n ;i++) 40 { 41 scanf("%d",&a[i]); 42 } 43 while(isprime(mod)==0) mod++; //找大于题目给的数的最小素数; 44 for(int i = 0 ; i < n ; i++) 45 { 46 fl = 0; 47 for(int j = 0 ; j <= mod ;j++) 48 { 49 if(vis[(a[i]+j*j)%mod]==0) //利用二次探测法;如果位置没被占用; 50 { 51 printf("%d",(a[i]+j*j)%mod); 52 vis[(a[i]+j*j)%mod] = 1; 53 fl = 1; 54 break; 55 } 56 } 57 if(fl==0) //如果找不到位置 58 { 59 printf("-"); 60 } 61 if(i!=n-1) //格式问题; 62 { 63 printf(" "); 64 } 65 } 66 return 0; 67 }
实践题:
实现QQ新帐户申请和老帐户登陆的简化版功能。最大挑战是:据说现在的QQ号码已经有10位数了。
输入格式:
输入首先给出一个正整数N(≤105),随后给出N行指令。每行指令的格式为:“命令符(空格)QQ号码(空格)密码”。其中命令符为“N”(代表New)时表示要新申请一个QQ号,后面是新帐户的号码和密码;命令符为“L”(代表Login)时表示是老帐户登陆,后面是登陆信息。QQ号码为一个不超过10位、但大于1000(据说QQ老总的号码是1001)的整数。密码为不小于6位、不超过16位、且不包含空格的字符串。
输出格式:
针对每条指令,给出相应的信息:
1)若新申请帐户成功,则输出“New: OK”;
2)若新申请的号码已经存在,则输出“ERROR: Exist”;
3)若老帐户登陆成功,则输出“Login: OK”;
4)若老帐户QQ号码不存在,则输出“ERROR: Not Exist”;
5)若老帐户密码错误,则输出“ERROR: Wrong PW”。
输入样例:
5
L 1234567890 myQQ@qq.com
N 1234567890 myQQ@qq.com
N 1234567890 myQQ@qq.com
L 1234567890 myQQ@qq
L 1234567890 myQQ@qq.com
输出样例:
ERROR: Not Exist
New: OK
ERROR: Exist
ERROR: Wrong PW
Login: OK
对于这道题,我偷懒用map去写的,然后看了网上大佬的代码,学习用哈希表去写
以下是我的代码:
1 #include<iostream> 2 #include<stdio.h> 3 #include<map> 4 using namespace std; 5 6 7 int N; 8 string s; 9 const int maxn = 1e5+5; 10 map<string,bool>mp; 11 struct info{ 12 string zh; 13 string password; 14 }in[maxn]; 15 string tmp; 16 int count1 = 0; 17 string tp1,tp2; 18 int flag = 0; 19 int main() 20 { 21 scanf("%d",&N); 22 while(N--) 23 { 24 flag = 0; 25 cin>>s; 26 if(s[0]=='N') 27 { 28 cin>>tp1>>tp2; 29 in[count1].zh = tp1; 30 in[count1++].password = tp2; 31 if(mp[tp1]==1) 32 { 33 printf("ERROR: Exist\n"); 34 }else 35 { 36 mp[tp1] = 1; 37 printf("New: OK\n"); 38 } 39 40 }else 41 if(s[0]=='L') 42 { 43 cin>>tp1>>tp2; 44 if(mp[tp1]==0) 45 { 46 printf("ERROR: Not Exist\n"); 47 }else 48 if(mp[tp1]==1) 49 { 50 for(int i = 0 ; i < count1;i++) 51 { 52 if(in[i].zh==tp1) 53 { 54 tmp = in[i].password; 55 flag = 1; 56 break; 57 } 58 } 59 if(tmp==tp2) 60 { 61 printf("Login: OK\n"); 62 }else 63 { 64 printf("ERROR: Wrong PW\n"); 65 } 66 } 67 } 68 } 69 return 0; 70 }
下面是网上别人用哈希表写的代码(我顺便学习了一下)
代码如下:
#include <cstdio> #include <cstdlib> #include <string.h> typedef struct node { char user[11]; char password[17]; struct node* next; } Node; typedef struct { int TableSize; Node* Table; } HashTable; int nextPrime( int n ) { while( 1 ) { bool flag = true; for( int i = 2; i < n; i++ ) { if( n % i == 0 ) { flag = false; break; } } if( flag ) break; n++; } return n; } HashTable* createHashTable( int n ) { HashTable* H = ( HashTable* )malloc( sizeof( HashTable ) ); H->TableSize = nextPrime( n ); H->Table = ( Node* )malloc( ( H->TableSize ) * sizeof( Node ) ); for( int i = 0; i < H->TableSize; i++ ) { H->Table[i].next = NULL; H->Table[i].user[0] = '\0'; H->Table[i].password[0] = '\0'; } return H; } int Hash( char key[] ) { int index = 0; for( int i = 0; i <= 3; i++ ) { index = index * 10 + key[i] - '0'; } return index; } Node* Find( HashTable* H, char key[] ) { int index = Hash( key ) % ( H->TableSize ); Node* ptr = H->Table[index].next; while( ptr && strcmp( ptr->user, key ) ) { ptr = ptr->next; } return ptr; } void Insert( HashTable* H, char key[], char pass[] ) { int index = Hash( key ) % ( H->TableSize ); Node* newNode = ( Node* )malloc( sizeof( Node ) ); newNode->next = H->Table[index].next; H->Table[index].next = newNode; strcpy( newNode->user, key ); strcpy( newNode->password, pass ); } int main() { //freopen( "123.txt", "r", stdin ); int n; scanf( "%d", &n ); HashTable* H = createHashTable( n ); //printf( "%d\n", H->TableSize ); char op; char account[20]; char key[20]; for( int i = 0; i < n; i++ ) { char ch = getchar(); scanf( "%c %s %s", &op, account, key ); //printf( "%s %s\n", account, key ); if( op == 'L' ) { Node* p = Find( H, account ); if( !p ) printf( "ERROR: Not Exist\n" ); // 老账户号码不存在 else if( strcmp( p->password, key ) ) { // 老账户密码错误 printf( "ERROR: Wrong PW\n" ); } else { // 登录成功 printf( "Login: OK\n" ); } } if( op == 'N' ) { Node* p = Find( H, account ); if( p ) { // 申请的账户已经存在 printf( "ERROR: Exist\n" ); } else { Insert( H, account ,key ); printf( "New: OK\n" ); } } } return 0; }
对于这一章的学习,我觉得我学得比较浅,大多都是概念性的东西,没有具体到代码实现,而且哈希表挺重要的,我还不会自己实现,我会去进一步去自己实现一遍哈希表;
对于下一章的学习,收心学习,会代码实现,灵活应用;