王道数据结构源码实战ch7查找
二叉排序(搜索)树BST
节点结构体定义
- 和普通二叉树完全一样
typedef struct BSTNode
{
KeyType key;
struct BSTNode *lchild,*rchild;
} BSTNode,*BiTree;
插入节点(递归)
- 递归出口:当前的节点T==NULL。待插入节点的值小于当前节点的值就向坐子树递归,否则向右子树递归,这里不考虑相同值的情况。
int BST_Insert(BiTree &T,KeyType x) //在创建中使用的单个元素插入功能
{
if(T==NULL) //走到合适的位置插入
{
T=(BiTree)malloc(sizeof(BSTNode)) ; //为新节点申请空间,第一个节点作为树根
T->key=x;
T->lchild=NULL;
T->rchild=NULL;
return 1; //根节点插入成功
}
else if(x==T->key)//相同元素不考虑
return 0;
else if(x<T->key)//小于当前节点的值,递归一路向左
return BST_Insert(T->lchild,x); //函数调用结束后,左儿子和父亲节点关联起来
else if(x>T->key)//大于当前节点的值,递归一路向右
return BST_Insert(T->rchild,x);
}
创建二叉排序树
void Creat_BST(BiTree &T,KeyType str[],int n) //创建二叉树
{
T=NULL;
for(int i=0; i<n; i++)
BST_Insert(T,str[i]);
}
搜索有没有值为key的元素(非递归)
- 不仅要找到值为key的节点,还要找到这个节点的父亲,通过引用传出来
BSTNode* BST_Search(BiTree T,KeyType key,BSTNode* &p) //查找有没有值为key的元素,有的话并把父亲也找到
{
p=NULL; //工作指针,存储要找的节点的父亲
while(T!=NULL&&key!=T->key) //当前节点非空,且该节点是数据不是key
{
p=T; //不断更新为当前节点,找到的情况,记录key的父节点;没找到记录最后访问的叶子节点
if(key<T->key)
T=T->lchild;
else
T=T->rchild;
}
return T; //没找到的情况,T==NULL
}
删除节点(书上没有)
- 根节点不为空,通过递归不断寻找待删除的节点
- 找到待删除节点后,分三种情况
i:左子树为空,直接拿右子树接替删除的节点
ii:右子树为空,直接拿左子树接替删除的节点
iii:左右子树都不为空,左子树的最大节点(左子树一路向右)接替删除的节点,因为被删除的节点本来一定大于左子树所有节点,现在左子树的最大节点(包含根在内的第二大节点)上移,也能保证它大于左子树的剩余节点,且小于右子树所有节点。对于下面的子树,递归的向上补位。
同理,用右子树的最小节点来代替也是可以的。
void DeleteNode(BiTree &root,KeyType x) //树根也可能被删除,所以需要引用
{
if(root==NULL)
return;
if(x<root->key)
DeleteNode(root->lchild,x);
else if(x>root->key)
DeleteNode(root->rchild,x); //不断递归寻找待删除节点的过程
else //找到了待删除节点
{
if(root->lchild==NULL) //左子树为空
{
BiTree tempNode = root ; //临时存储是为了free ,和链表删除一样
root=root->rchild;
free(tempNode);
}
else if(root->rchild==NULL) //右子树为空
{
BiTree tempNode = root ;
root=root->lchild;
free(tempNode);
}
else //左右子树都不为空时
//左子树的最大节点(左子树一路向右),或右子树的最小节点(右子树一路向左)来代替
{
BiTree tempNode = root->lchild ;
if(tempNode->rchild!=NULL) //一路向右,直到取得NULL,那么它的父亲就是我们找的
tempNode=tempNode->rchild;
root->key= tempNode->key; //直接用左子树的最大节点的数据填充删除节点
DeleteNode(root->lchild,tempNode->key); //递归地删除每一个向上面补充的节点
}
}
}
完整代码
#include<bits/stdc++.h>
using namespace std;
typedef int KeyType;
typedef struct BSTNode
{
KeyType key;
struct BSTNode *lchild,*rchild;
} BSTNode,*BiTree;
int BST_Insert(BiTree &T,KeyType x) //在创建中使用的单个元素插入功能
{
if(T==NULL) //走到合适的位置插入
{
T=(BiTree)malloc(sizeof(BSTNode)) ; //为新节点申请空间,第一个节点作为树根
T->key=x;
T->lchild=NULL;
T->rchild=NULL;
return 1; //根节点插入成功
}
else if(x==T->key)//相同元素不考虑
return 0;
else if(x<T->key)//小于当前节点的值,递归一路向左
return BST_Insert(T->lchild,x); //函数调用结束后,左儿子和父亲节点关联起来
else if(x>T->key)//大于当前节点的值,递归一路向右
return BST_Insert(T->rchild,x);
}
void Creat_BST(BiTree &T,KeyType str[],int n) //创建二叉树
{
T=NULL;
for(int i=0; i<n; i++)
BST_Insert(T,str[i]);
}
BSTNode* BST_Search(BiTree T,KeyType key,BSTNode* &p) //查找有没有值为key的元素,有的话并把父亲也找到
{
p=NULL; //工作指针,存储要找的节点的父亲
while(T!=NULL&&key!=T->key) //当前节点非空,且该节点是数据不是key
{
p=T; //不断更新为当前节点,找到的情况,记录key的父节点;没找到记录最后访问的叶子节点
if(key<T->key)
T=T->lchild;
else
T=T->rchild;
}
return T; //没找到的情况,T==NULL
}
void InOrder(BiTree T)
{
if(T!=NULL)
{
InOrder(T->lchild);
cout<<T->key<<" ";
InOrder(T->rchild);
}
}
void DeleteNode(BiTree &root,KeyType x) //树根也可能被删除,所以需要引用
{
if(root==NULL)
return;
if(x<root->key)
DeleteNode(root->lchild,x);
else if(x>root->key)
DeleteNode(root->rchild,x); //不断递归寻找待删除节点的过程
else //找到了待删除节点
{
if(root->lchild==NULL) //左子树为空
{
BiTree tempNode = root ; //临时存储是为了free ,和链表删除一样
root=root->rchild;
free(tempNode);
}
else if(root->rchild==NULL) //右子树为空
{
BiTree tempNode = root ;
root=root->lchild;
free(tempNode);
}
else //左右子树都不为空时
//左子树的最大节点(左子树一路向右),或右子树的最小节点(右子树一路向左)来代替
{
BiTree tempNode = root->lchild ;
if(tempNode->rchild!=NULL) //一路向右,直到取得NULL,那么它的父亲就是我们找的
tempNode=tempNode->rchild;
root->key= tempNode->key; //直接用左子树的最大节点的数据填充删除节点
DeleteNode(root->lchild,tempNode->key); //递归地删除每一个向上面补充的节点
}
}
}
int main()
{
BiTree T=NULL; //树根
BiTree parent; //存储父亲节点的地址
BiTree search;
KeyType str[]= {54,20,66,40,28,79,58};
Creat_BST(T,str,7); //创建
InOrder(T);
cout<<endl;
search=BST_Search(T,40,parent); //查找并不是遍历
if(search!=NULL)
cout<<search->key<<" is found"<<endl;
else
cout<<"not found"<<endl;
DeleteNode(T,79); //删除时候,需要知道父亲节点,以便把下面的子树接上去
InOrder(T);
cout<<endl;
return 0;
}
顺序查找
int Search_Seq(SSTable ST,ElemType key) //顺序查找key的位置
{
int i=0;
ST.elem[0]=key; //放置哨兵,保证查找不会越界
for(i=ST.TableLen-1; ST.elem[i]!=key; i--); //空循环,查找失败,i=0;
return i;
}
关于srand()和rand()
- 所在的头文件#include<stdlib.h>
- 初始化随机数发生器srand() :用来设置rand()产生随机数时的随机数种子,改变了随机数种子,才能产生新的一组随机数
- 随机数发生器 rand() :和srand()要一起使用,其中srand()用来初始化随机数种子,rand()用来产生随机数
折半查找
int Binary_Search(SSTable L,ElemType key) //二分查找
{
int low=0,high=L.TableLen-1,mid;
while(low<=high)
{
mid=(low+high)/2;
if(L.elem[mid]==key)
return mid;
else if(L.elem[mid]<key)
low=mid+1; //确定是在中值的右边,左端点压缩到原中点的右边一个
else
high=mid-1; //确定是在中值的左边,右边点压缩到原中点的左边一个
}
}
关于sort()
-
sort(ST.elem,ST.elem+ST.TableLen,compare); //自定义比较函数
int cmp(const int &a,const int &b) //自定义的降序函数
{
return a>b
} -
C++11模板 升序:sort(s.begin(),s.end(),less<data_type>())
降序:sort(s.begin(),s.end(),greater<data_type>())
完整代码(顺序+折半查找)
#include<bits/stdc++.h>
using namespace std;
#include<algorithm>
typedef int ElemType;
typedef struct BSTNode
{
ElemType *elem; //存储首地址,动态数组,用的是堆空间
int TableLen;
} SSTable;
int Search_Seq(SSTable ST,ElemType key) //顺序查找key的位置
{
int i=0;
ST.elem[0]=key; //放置哨兵,保证查找不会越界
for(i=ST.TableLen-1; ST.elem[i]!=key; i--); //空循环,查找失败,i=0;
return i;
}
int Binary_Search(SSTable L,ElemType key) //二分查找
{
int low=0,high=L.TableLen-1,mid;
while(low<=high)
{
mid=(low+high)/2;
if(L.elem[mid]==key)
return mid;
else if(L.elem[mid]<key)
low=mid+1; //确定是在中值的右边,左端点压缩到原中点的右边一个
else
high=mid-1; //确定是在中值的左边,右边点压缩到原中点的左边一个
}
}
void ST_Init(SSTable &ST,int len) //初始化顺序表
{
ST.TableLen=len+1; //多一个位置存储哨兵
ST.elem=(ElemType *)malloc(sizeof(ElemType)*ST.TableLen);
srand(time(NULL)); //随机数生成,没有这一步,每次的随机数是一样的
for(int i=0; i<ST.TableLen; i++)
ST.elem[i]=rand()%100;
}
int cmp(const void *a,const void *b) //qsort的行为参数
{
return *(ElemType*)a-*(ElemType*)b; //写大于号会出问题,(a-b)<0,a<b,从小到大
}
bool cmp2(int a,int b) //sort的行为参数,与上面的cmp相反
{
return a-b; //(a-b)>0,a>b,从大到小
}
int main()
{
SSTable ST;
ST_Init(ST,10);
for(int i=0; i<=10; i++)
cout<<ST.elem[i]<<" ";
cout<<endl;
//顺序查找的测试
int key;
cin>>key;
int pos=Search_Seq(ST,key);
if(pos!=0)
cout<<"search succuss,the position is "<<pos<<endl;
else
cout<<"search fail"<<endl;
//折半查找的测试
qsort(ST.elem,ST.TableLen,sizeof(int),cmp); //快排,compare是传递一种行为
for(int i=0; i<=10; i++)
cout<<ST.elem[i]<<" ";
cout<<endl;
//sort(ST.elem,ST.elem+ST.TableLen,cmp2);
//sort(s.begin(),s.end(),less<int>()); //降序模版
//sort(s.begin(),s.end(),greater<int>()); //升序模版
int key2;
cin>>key2;
int pos2=Binary_Search(ST,key2);
if(pos2!=0)
cout<<"search succuss,the position is "<<pos2<<endl;
else
cout<<"search fail"<<endl;
return 0;
}
哈希(散列)查找
- 什么是散列函数 :把查找表中关键字映射为该关键字对应的地址
- 什么是散列冲突 :不同元素经过散列函数计算的地址是同一个。无冲突的情况下,查找时间复杂度为O(1)
- 怎么解决散列冲突:开放定址法;链地址法
#include<iostream>
using namespace std;
#define MaxKey 1000
int Hash(const char* key) //业界常用的哈希函数
{
int h=0,g;
while(*key) //把每一个字节的值拿出来
{
h=(h<<4)+*key++;
g=h&0xf0000000;
if(g)
{
h^=g>>24;
}
h&=~g;
}
return h% MaxKey; //算出来的数组下标必须在0-999之间
}
int main()
{
const char *pStr[5]= {"xiongda","lele","hanmeimei","wangdao","fangfang"}; //定义字符串数组,const表示不能改变里面的字符串
const char *Hash_table[MaxKey]= {NULL}; //建立哈希表,并清空
for(int i=0; i<5; i++)
{
printf("%s is key=%d\n",pStr[i],Hash(pStr[i]));
Hash_table[Hash(pStr[i])]=pStr[i]; //把字符串放到哈希表对应计算出的下标的位置
}
cout<<Hash_table[377]<<endl;
return 0;
}