第八章:查找
线性表查找
顺序查找
/*
线性表查找:
1. 顺序查找
2. 二分查找
*/
#include <iostream>
using namespace std;
#define MAXSIZE 100
//顺序查找
int SqSearch(int r[],int n, int x)
{
for (int i = 0; i < n; i++)
{
if (r[i] == x)
return i;
return -1;
}
}
//顺序查找算法优化
int SqSearch2(int r2[], int n, int x)
{
int i;
r2[0] = x;//待查找元素放入r[0],作为监视哨
for (i = n; r2[i] != x; i--);//不需要判断i是否超过范围
return i;
}
int main()
{
int i, x, n, r[MAXSIZE], r2[MAXSIZE];
cout << "请输入元素个数 n: " << endl;
cin >> n;
cout << "请依次输入 n 个元素:" << endl;
for (int i = 0; i < n; i++)
{
cin >> r[i];
r2[i + 1] = r[i]; // r2[]数组0空间未用,做监视哨
}
cout << endl;
cout << "请输入要查找的元素 x : " << endl;
cin >> x;
//顺序查找
//i = SqSearch(r, n, x);
//if (i == -1)
// cout << "未找到该元素!" << endl;
//else
// cout << "该元素在第" << i << " 位" << endl;
//顺序查找算法优化
i = SqSearch2(r2, n, x);
if (i == 0)
cout << "未找到该元素!" << endl;
else
cout << "该元素在第" << i << " 位" << endl;
return 0;
}
二分查找
必须是有序序列
/*
线性表查找:
1. 顺序查找
2. 二分查找
*/
#include <iostream>
#include<algorithm>
using namespace std;
const int M = 100;
int x, n, i;
int s[M];
//二分查找(非递归算法)
int BinarySearch(int s[], int n, int x)
{
int low = 0; //low指向有序数组的第一个元素,high指向有序数组的最后一个元素
int high = n - 1;
while (low <= high)
{
int middle = (low + high) / 2;
if (x == s[middle]) //x等于查找范围的中间值,算法结束
return middle;
else if (x > middle) //x大于查找范围的中间元素,则从左半部分查找
low = middle + 1;
else //x小于查找范围的中间元素,则从右半部分查找
high = middle - 1;
}
return -1;
}
//二分查找(递归算法)
int recursionBS(int s[], int x, int low, int high)
{
//low指向有序数组的第一个元素,high指向有序数组的最后一个元素
if (low > high)
return -1;
int middle = (low + high) / 2;
if (x == s[middle])
return middle;
else if (x < middle)
return recursionBS(s, x, low, middle - 1);
else
return recursionBS(s, x, middle + 1, high);
}
int main()
{
cout << "请输入元素个数 n: " << endl;
cin >> n;
cout << "请依次输入 n 个元素:" << endl;
for (int i = 0; i < n; i++)
{
cin >> s[i];
}
sort(s, s + n); //sort:从小到大,二分查找的序列必须是有序的,如果无序需要先排序
cout << "排序后的数组为:" << endl;
for (int i = 0; i < n; i++)
{
cout << s[i] << " ";
}
cout << endl;
cout << "请输入要查找的元素 x : " << endl;
cin >> x;
//i=BinarySearch(s,n,x); // 二分查找非递归
i = recursionBS(s, x, 0, n - 1); // 二分查找递归
if (i == -1)
cout << "未找到" << endl;
else
cout << "在第" << i+1 << "位置找到" << endl;
return 0;
}
树表查找
二叉查找树
/*
树表查找:
1.二叉查找树
2. 平衡二叉查找树
*/
#include <iostream>
using namespace std;
#define ENDFLAG -1
typedef int ElemType;
typedef struct BSTNODE
{
ElemType data; //结点数据域
BSTNODE* lchild, * rchild; // 左右孩子指针
}BSTNODE,*BSTree;
// 二叉排序树的插入
// 当二叉排序树T中不存在关键字等于e的数据元素时,则插入该元素
void InsertBST(BSTree& T, ElemType e)
{
if (!T) //如果为空
{
BSTree s = new BSTNODE; //生成新结点
s->data = e;
s->lchild = s->rchild = NULL;
T = s;
}
else if (e < T->data)
InsertBST(T->lchild, e);
else
InsertBST(T->rchild, e);
}
//二叉排序树的创建
//依次读入一个关键字为key的结点,将此结点插入二叉排序树T中
void CreateBST(BSTree& T)
{
T = NULL;
ElemType e;
cin >> e;
while (e != ENDFLAG)
{
InsertBST(T, e);
cin >> e;
}
}
//二叉排序树的递归查找
//若查找成功,则返回指向该数据元素结点的指针,否则返回空指针
BSTree SearchBST(BSTree& T, ElemType key)
{
if ((!T) || key == T->data)
return T;
else if (key < T->data)
return SearchBST(T->lchild, key);
else
return SearchBST(T->rchild, key);
}
// 二叉查找树的删除
//从二叉排序树T中删除关键字等于key的结点
void DeleteBST(BSTree& T, ElemType key)
{
BSTree p = T; //参数初始化, p 指向要删除的结点
BSTree f = NULL; // f 指向 p 的双亲结点
BSTree q;
BSTree s; // s 指向 p 的直接前驱, q 指向 s 的双亲
if (!T) return; // //树为空则返回
while (p) // 查找
{
if (key == p->data) break; //找到关键字等于key的结点p,结束循环
f = p; // f 为 p 的双亲
if (key < p->data)
p = p->lchild;
else
p = p->rchild;
}
if (!p) return; //找不到被删结点则返回
// 3 种情况:p左右子树均不空、无右子树、无左子树
// p 左右子树均不空
// 特殊情况 ,p 的左孩子没有右子树,其左子树 s 就是前驱
if ((p->lchild) && (p->rchild))
{
q = p;
s = p->lchild;
while (s->rchild) //在p的左子树中继续查找其前驱结点,即最右下结点
{
q = s; // q 是 s 的双亲
s = s->rchild;
}
p->data = s->data; //s的值赋值给被删结点p,然后删除s结点
if (q != p) // 重建 p 的子树
q->rchild = s->lchild;
else
q->lchild = s->lchild;
delete s;
}
else
{
if (!p->rchild) //被删结点p无右子树,只需重接其左子树
{
q = p;
p = p->lchild;
}
else if (!p->lchild) //被删结点p无左子树,只需重接其右子树
{
q = p;
p = p->rchild;
}
/*――――――――――将p所指的子树挂接到其双亲结点 f 相应的位置――――――――*/
if (!f)
T = p; //被删结点为根结点
else if (q == f->lchild)
f->lchild = p; //挂接到f的左子树位置
else
f->rchild = p; //挂接到f的右子树位置
delete q;
}
}
//中序遍历
void InOrderTraverse(BSTree &T)
{
if (T)
{
InOrderTraverse(T->lchild);
cout << T->data << "\t";
InOrderTraverse(T->rchild);
}
}
int main()
{
BSTree T;
cout << "请输入一些整数,-1 结束" << endl;
CreateBST(T);
cout << "当前有序二叉树中序遍历结果为:" << endl;
InOrderTraverse(T);
cout << endl;
ElemType key;//待查找或待删除内容
cout << "请输入要查找的关键字:" << endl;
cin >> key;
BSTree result = SearchBST(T, key);
if (result)
cout << "找到" << key << endl;
else
cout << "未找到" << key << endl;
cout << "请输入要删除的关键字:" << endl;
cin >> key;
DeleteBST(T, key);
cout << "当前有序二叉树中序遍历结果为" << endl;
InOrderTraverse(T);
return 0;
}
平衡二叉查找树
C++ 内联函数 (inline 关键字介绍)
在 C++ 中,可以在定义函数时,在返回值类型前面加上 inline 关键字。如:
inline int Max(int a, int b)
{
if(a>b)
return a;
return b;
}
增加了 inline 关键字的函数称为“内联函数”。内联函数和普通函数的区别在于:当编译器处理调用内联函数的语句时,不会将该语句编译成函数调用的指令,而是直接将整个函数体的代码插人调用语句处,就像整个函数体在调用处被重写了一遍一样。
有了内联函数,就能像调用一个函数那样方便地重复使用一段代码,而不需要付出执行函数调用的额外开销。很显然,使用内联函数会使最终可执行程序的体积增加。以时间换取空间,或增加空间消耗来节省时间,这是计算机学科中常用的方法。
内联函数中的代码应该只是很简单、执行很快的几条语句。如果一个函数较为复杂,它执行的时间可能上万倍于函数调用的额外开销,那么将其作为内联函数处理的结果是付出让代码体积增加不少的代价,却只使速度提高了万分之一,这显然是不划算的。
有时函数看上去很简单,例如只有一个包含一两条语句的循环,但该循环的执行次数可能很多,要消耗大量时间,那么这种情况也不适合将其实现为内联函数。
另外,需要注意的是,调用内联函数的语句前必须已经出现内联函数的定义(即整个数体),而不能只出现内联函数的声明。
程序暂未搞懂,后续添加
散列表的查找
处理冲突的方法
/*
散列表(哈希表)
表中包含三行内容:
- 哈希地址
- 关键字
- 比较次数
处理冲突的方法:
1. 开放地址法:(线性探测法,二次探测法,随机探测法,再散列法)
2. 链地址法
3. 建立公共溢出区
*/
#include <iostream>
using namespace std;
#define m 15 // 哈希表的表长
#define NULLKEY 0 // 单元为空的标记
// HT[]:哈希表中,存放关键字
// HC[]: 统计比较次数
int HT[m], HC[m];
int H(int Key) // 哈希函数
{
return Key % 13;
}
// 1. 线性探测法(开发地址法)
int Linedetect(int HT[], int H0, int key, int& cnt)
{
int Hi; //用于存放新的哈希地址
for (int i = 1; i < m; i++)
{
cnt++; // 统计次数 +1
// 按照线性探测计算下一个散列地址 Hi
Hi = (H0 + i) % m; // hash`(key) = (hash(key) + i)%m
if (HT[Hi] == NULLKEY)
return Hi;
else if (HT[Hi] == key)
return Hi;
}
return -1;
}
/*
解决冲突方法二:
2. 二次探测法
*/
int Seconddectect(int HT[], int H0, int key, int& cnt)
{
int Hi;
for (int i = 1; i < m / 2; i++)
{
int i1 = i * i;
int i2 = -i1;
cnt++;
Hi = (H0 + i1) % m;
if (HT[Hi] == NULLKEY)
return Hi;
else if (HT[Hi] == key)
return Hi;
cnt++;
Hi = (H0 + i2) % m;
if (Hi < 0)
Hi += m;
if (HT[Hi] == NULLKEY)
return Hi;
else if (HT[Hi] == key)
return Hi;
}
return -1;
}
//根据哈希函数计算哈希地址,用于存放关键字
//创建哈希表
bool InsertHash(int HT[], int key)
{
int H0 = H(key); // 根据哈希函数 H(key) 计算哈希地址
int Hi = -1, cnt = 1; // Hi 不能再初始为0了,因为 HT[]已经初始为0,
//否则某些key值会使后面的 if 条件不成立
if (HT[H0] == NULLKEY)
{
HC[H0] = 1; // 统计比较次数
HT[H0] = key; //若单元 H0 为空,放入
return 1;
}
else // 出现冲突
{
Hi = Linedetect(HT, H0, key, cnt); // 线性探测
//Hi = Seconddectect(HT, H0, key, cnt); // 二次探测
if ((Hi != -1) && (HT[Hi]) == NULLKEY)
{
HC[Hi] = cnt;
HT[Hi] = key;
return 1;
}
}
return 0;
}
// 查找哈希表元素
// 在哈希表 HT 中查找关键字为key 的元素,若找到,则返回下标,否则返回-1
int SearchHash(int HT[], int key)
{
int H0 = H(key);
int Hi, cnt = 1;
if (HT[H0] == NULLKEY) //若单元H0为空,则所查元素不存在
return -1;
else if (HT[H0] == key)
{
cout << "查找成功,比较次数:" << cnt << endl;
return H0;
}
else
{
Hi = Linedetect(HT, H0, key, cnt);
//Hi = Seconddectect(HT, H0, key, cnt);
if (HT[Hi] == key)
{
cout << "查找成功,查找次数:" << cnt << endl;
return Hi;
}
else
return -1;
}
}
//打印函数
void print(int HT[])
{
for (int i = 0; i < m; i++)
{
cout << HT[i] << "\t";
}
cout << endl;
}
int main()
{
int x;
memset(HT, 0, sizeof(HT));
memset(HC, 0, sizeof(HC));
print(HT);
//输入
cout << "输入12个关键字,存入哈希表中:" << endl;
for (int i = 0; i < 12; i++)
{
cin >> x; //14 36 42 38 40 15 19 12 51 65 34 25
if (!InsertHash(HT,x))
{
cout << "创建哈希表失败!" << endl;
return 0;
}
}
//输出
cout << "输出哈希表:" << endl;
print(HT);
print(HC);
//查找
while (x != -1) // 输入-1, 停止查找
{
cout << "输入要查找的关键字:" << endl;
cin >> x;
int result = SearchHash(HT, x);
if (result != -1)
cout << "在第" << result + 1 << "个位置找到" << endl;
else
cout << "未找到" << endl;
}
return 0;
}