数据结构与算法-查找
往期内容
1-链表
2-栈与队列
3-树与图
4-哈希表
5-查找
6-排序
7-贪心
8-递归与分治
9-动态规划
查找
一、常用的查找方法
1.1入门查找
- 常规
int Sequential_Search(int *a,int n,int key)
{
for(int i=0;i<n;i++)
{
if(a[i]==key)
return i;
}
//否则返回0:错误
return 0;
}
- 哨兵
//哨兵
//优化后的顺序查找
int Sequential_Search_Optimize(int *a,int n,int key)
{
a[0]=key;//哨兵
int i=n;
while(a[i]!=a[0])
{
i--;
}
return i;//返回0表示查找失败
}
1.2 二分查找
//1.基本的二分查找
int binarySearch(int *arr,int n,int targer)
{
int left=0,right=n-1;//搜索空间全为闭
while(left<=right)//相当于两个区间上全部为闭区间
{
int mid=left+(right-left)/2;//可有效防止整型相加溢出风险
if(arr[mid]==targer)//退出条件
return mid;
else if(arr[mid]>targer)//区间向左减小条件
right=mid-1;
else if (arr[mid]<targer)//区间向右减小条件
{
left=mid+1;
}
}
return -1;//计算错误
}
//2.递归写法
//2.二分查找递归代码实现
bool search(int *arr,int begin,int end,int target)
{
if(begin>end)
return false;
int mid=(begin+end)/2;
if(target==arr[mid])
return true;
else if(target<arr[mid])
{
return search(arr,begin,mid-1,target);
}
else //if(target>arr[mid])
{
return search(arr,mid+1,end,target);
}
}
bool binary_search(int *arr,int n,int target)
{
return search(arr,0,n-1,target);
}
1.3二叉排序树
1.3.1性质
- 若她的左子树不为空,左子树上的所有值均小于它根结点的值
- 若她的右子树不为空,右子树上的所有值均大于它根结点的值
- 它的左右子树也分别为二叉排序树
1.3.2 结构
二叉排序树的实质,还是一个二叉树,因此它的结构依旧是树的结构
struct TreeNode{
int val;//值
TreeNode* left;
TreeNode* right;
};
1.3.3 查找
二叉排序树的查找和二分查找的代码结构类似,带查找值和根结点比较,大于根结点访问右子树,小于根结点访问左子树
//1.查找
//root:树的根节点,key:需要查找的元素,parent:指向当前root的双亲结点,初始化为null,posNode:查找成功的结点
bool SearchBST(TreeNode* root,int key,TreeNode* parent,TreeNode** posNode)
{
//递归:第一步找到边界条件
if(root==NULL){
*posNode=parent;//未找到时,当前结点的指针指向最终的叶子结点
return false;
}
else if(root->val==key)//找到了该元素
{
*posNode=root;//找到时,当前结点的指针指向该结点
return true;
}
else if(key<root->val)//左边查找
{
return SearchBST(root->left,key,root,posNode);
}
else
{
return SearchBST(root->right,key,root,posNode);
}
}
1.3.4 插入
//2.插入操作
bool InsertBST(TreeNode** root,int key)
{
TreeNode* p;//遍历查找时使用
TreeNode* s;//创建新结点时使用
if(!SearchBST(*root,key,NULL,&p))//如果当前结点元素不在树中,插入
{
s=(TreeNode*)malloc(sizeof(TreeNode));
s->val=key;
s->left=NULL;//不要忘记了
s->right=NULL;
if(p==NULL)
{
*root=s;
}
else if(p->val>key) //左边插入
{
p->left=s;
}
else//右结点插入
{
p->right=s;
}
return true;
}
else{
return false;
}
}
1.3.5 删除
请神容易,送神难,删除需要考虑多种情况
- 要删除结点只有左子树或者只有右子树(子承父业),将左子树或者右子树整体移动到待删除结点的位置即可。
- 如果既有左子树,又有右子树
//3.二叉树的删除操作
//如何删除单个结点-》》》》》》情况一,只有左孩子或者右孩子,情况二,有两个孩子
bool Delete(TreeNode **root)
{
//定义两个指向结点的指s针
TreeNode* s;
TreeNode* q;//释放结点内存的指针
if((*root)->left==NULL)
{
q=*root;
*root=(*root)->right;//指向下一个左孩子
free(q);
}
else if((*root)->right==NULL)
{
q=*root;
*root=(*root)->left;
free(q);
}
else{
//使用左子树中最大的元素替代该节点
s=*root;
q=(*root)->left;//转到左边结点
while(q->right)//找到右结点为空的结点(最大的结点q),s为q的前向结点
{
s=q;//q指向待结点的前驱结点
q=q->right;
}
//搬移数据
(*root)->val=q->val;
//结点联结,删除结点
if(*root!=s)//如果root结点的左子结点无右结点:
{
//链接左节点
s->right=q->left;
}
else{ //如果root结点的左子结点有右结点
s->left=q->left;
}
free(q);//回收q的指向,当前的结点
}
return true;
}
//如何删除某个结点
bool DeleteBST(TreeNode** root,int key)
{
if(*root==NULL) return false;
else
{
if((*root)->val==key)
return Delete(root);
else if(key<(*root)->val)
return DeleteBST(&((*root)->left),key);
else
return DeleteBST(&((*root)->right),key);
}
}
1.4 AVL平衡二叉树
平衡二叉树是一种二叉排序树,其中每个结点的左子树和右子树的高度差的绝对值至多等于1 注意:(0,-1,+1)
1.4.1 平衡二叉树的结构
多增加了一个变量用于存放平衡因子
//定义一个树的结构,但是要增加一个bf用作:结点的平衡因子
struct BiTNode{
int data;
int bf;//计算平衡因子
BiTNode *left,*right;
};
1.4.2 四种保证平衡的方法
因此
- 单右旋
//BF>1:左高右低,右旋
void R_Rotate(BiTNode **root)//二级指针,需要改变树的结构
{
BiTNode* L;//左子树结点指针
L=(*root)->left;
(*root)->left=L->right;
L->right=(*root);
*root=L;
}
- 单左旋
//BF<1:左低右高,左旋
void L_Rotate(BiTNode **root)
{
BiTNode* R;//左子树结点指针
R=(*root)->right;
(*root)->right=R->left;
R->left=(*root);
*root=R;
}
- 左平衡
//1.左平衡操作
void LeftBalance(BiTNode** root)
{
BiTNode *L,*Lr;//左平衡的两个操作:右单旋,先左后右旋转
L=(*root)->left;
switch(L->bf)
{
case LH:
(*root)->bf=L->bf=EH;
R_Rotate(root);
break;
case RH:
Lr=L->right;
switch(Lr->bf)
{
case LH:
(*root)->bf=RH;
L->bf=EH;
break;
case EH:
(*root)->bf=EH;
L->bf=EH;
break;
case RH:
(*root)->bf=EH;
L->bf=LH;
break;
}
Lr->bf=EH;
L_Rotate(&(*root)->left);
R_Rotate(root);
}
}
- 右平衡
//2.右边平衡操作
void RightBalance(BiTNode** root)
{
BiTNode *R,*Rl;//左平衡的两个操作:右单旋,先左后右旋转
R=(*root)->right;
switch(R->bf)
{
case RH:
R->bf=(*root)->bf=EH;
L_Rotate(root);
break;
case LH:
Rl=R->left;
switch(Rl->bf)
{
case LH:
(*root)->bf=EH;
R->bf=RH;
break;
case EH:
(*root)->bf=EH;
R->bf=EH;
break;
case RH:
(*root)->bf=LH;
R->bf=EH;
break;
}
Rl->bf=EH;
R_Rotate(&(*root)->right);
L_Rotate(root);
}
}
1.4.3 算法
bool InsetAVL(BiTNode** root,int key,bool *taller)//参数三:判断树是否长高
{
if(!*root)//如果树为空
{
*root=(BiTNode*)malloc(sizeof(BiTNode));
(*root)->data=key;
(*root)->left=(*root)->right=NULL;
(*root)->bf=EH;
*taller=true;
}
else{
if((*root)->data==key) //如果当前结点值等于key,则不需要插入
{
*taller=false;
return false;
}
else if((*root)->data>key)//在左边插入
{
//左区间递归
if(!InsetAVL(&(*root)->left,key,taller))
return false;
if(taller)//树长高了?
{
//因为从左边插入,只需要检测左边的bf即可
switch((*root)->bf)
{
case LH:
LeftBalance(root);
(*root)->bf=LH;//保持不变,可以不写
*taller=false;
break;
case EH:
(*root)->bf=LH;
*taller=true;
break;
case RH:
(*root)->bf=EH;
*taller=false;
break;
}
}
}
else//在当前节点的右边插入
{
//右区间递归
if(!InsetAVL(&(*root)->right,key,taller))
return false;
if(taller)//树长高了?
{
//因为从右边边插入,只需要检测root的bf即可
switch((*root)->bf)
{
case LH:
(*root)->bf=EH;
*taller=false;
break;
case EH:
(*root)->bf=RH;
*taller=true;
break;
case RH:
RightBalance(root);
(*root)->bf=RH;//保持不变
*taller=false;
break;
}
}
}
}
return true;
}
1.5 散列表
存储位置=f(关键字)
处理哈希冲突:开放定址,找下一个位置。
再散列函数法:准备多个散列函数
链地址法:
二、Leetcode刷题
二分查找和搜索
2.1 搜索插入位置
Leetcode-35 搜索插入位置
思路:用一个标志位index记录,找到还是插入,二分查找:情况一:找到该元素,万事大吉。情况 二:未找到该元素,如果 target < nums[mid],且target > nums[mid - 1]和target > nums[mid],且target < nums[mid + 1];两种情况分开讨论
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int begin=0;
int end=nums.size()-1;
int index=-1;//返回的下标
while(index==-1)//还未找到正确的位置
{
int mid=begin+(end-begin)/2;
if(nums[mid]==target)
index=mid;
else if(target<nums[mid])
{
if(mid==0 || target>nums[mid-1])
index=mid;
end=mid-1;
}
else if(target>nums[mid])
{
if(mid==nums.size()-1 || target<nums[mid+1])
{
index=mid+1;
}
begin=mid+1;
}
}
return index;
}
};
2.2 在排序数组中查找元素的第一个和最后一个位置
Leetcode-34 在排序数组中查找元素的第一个和最后一个位置
思路1:暴力从前往后遍历
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
bool flag=false;
int begin=-1;
int end=-1;
for(int i=0;i<nums.size();i++)
{
if(nums[i]==target && !flag)
{
begin=i;
end =i;
flag=true;
}
if(flag)
{
if(nums[i]!=target)
{
break;
}
end=i;
}
}
vector<int> res;
res.push_back(begin);
res.push_back(end);
return res;
}
};
思路2:二分查找,当找到目标元素的时候,依次向前和向后遍历,直到找到区间
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> result={-1,-1};
int begin=0;
int end=nums.size()-1;
while(begin<=end)
{
int mid=begin+(end-begin)/2;
if(target==nums[mid])
{
int left=mid;
int right=mid;
while(left >0 && target==nums[left-1])
{
left=left-1;
}
while(right <(nums.size()-1) && target==nums[right+1])
{
right=right+1;
}
result[0]=left;
result[1]=right;
break;
}
else if(target>nums[mid])
{
begin=mid+1;
}
else if(target<nums[mid])
{
end=mid-1;
}
}
return result;
}
};
思路3:控制变量,找到左端点和右端点
2.3 搜索旋转排序数组
思路:二分查找,分情况讨论
class Solution {
public:
int search(vector<int>& nums, int target) {
if(nums.size()<0) return -1;
if (nums.size() == 1) {
return nums[0] == target ? 0 : -1;
}
int left = 0, right = nums.size() - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] == target) return mid;
if (nums[0] <= nums[mid]) {
if (nums[0] <= target && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
} else {
if (nums[mid] < target && target <= nums[nums.size() - 1]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
};
2.4 岛屿数量
思路1:深度优先遍历
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
vector<vector<int>> mark(grid.size(),vector<int>(grid[0].size(),0));
int count=0;
for(int i=0;i<grid.size();i++)
{
for(int j=0;j<grid[0].size();j++)
{
if(mark[i][j]==0 && grid[i][j]=='1')//注意条件
{
count++;
DFS_search(grid,mark,i,j);
}
}
}
return count;
}
void DFS_search(vector<vector<char>>& grid,vector<vector<int>>& mark,int x,int y)
{
mark[x][y]=1;
static const int dx[4]={0,0,-1,1};
static const int dy[4]={1,-1,0,0};
for(int i=0;i<4;i++)
{
int new_x=x+dx[i];
int new_y=y+dy[i];
if(new_x>=0 && new_y>=0 && new_x<mark.size() && new_y<mark[new_x].size())
{
if(mark[new_x][new_y]==0 && grid[new_x][new_y]=='1')//没有访问且是连通的(注意条件)
{
DFS_search(grid,mark,new_x,new_y);
}
}
}
}
};
思路2:广度优先遍历
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
vector<vector<int>> mark(grid.size(),vector<int>(grid[0].size(),0));
int count=0;
for(int i=0;i<grid.size();i++)
{
for(int j=0;j<grid[0].size();j++)
{
if(mark[i][j]==0 && grid[i][j]=='1')
{
count++;
BFS_search(grid,mark,i,j);
}
}
}
return count;
}
void BFS_search(vector<vector<char>>& grid,vector<vector<int>>& mark,int x,int y)
{
static const int dx[]={0,0,1,-1};
static const int dy[]={1,-1,0,0};
queue<pair<int,int>> Q;
Q.push(make_pair(x,y));
mark[x][y]=1;
while(!Q.empty())
{
x=Q.front().first;
y=Q.front().second;
Q.pop();
for(int i=0;i<4;i++)
{
int new_x=x+dx[i];
int new_y=y+dy[i];
if(new_x>=0 && new_y>=0 && new_x<grid.size() && new_y<grid[new_x].size())
{
if(mark[new_x][new_y]==0 && grid[new_x][new_y]=='1')
{
Q.push(make_pair(new_x,new_y));
mark[new_x][new_y]=1;
}
}
}
}
}
};
2.5 单词接龙
class Solution {
public:
//判断两个字符是否可以转换
bool isConnect(string &word1,string &word2){
int count=0;
for(int i=0;i<word1.size();i++)
{
if(word1[i]!=word2[i])
count++;
}
return count==1;
}
//创建图
void construct_graph(string &beginWord, vector<string>& wordList,
map<string,vector<string>> &graph){
wordList.push_back(beginWord);
for(int i=0;i<wordList.size();i++)
{
graph[wordList[i]]=vector<string>();
}
for(int i=0;i<wordList.size();i++)
{
for(int j=i+1;j<wordList.size();j++)
{
if(isConnect(wordList[i],wordList[j]))
{
graph[wordList[i]].push_back(wordList[j]);
graph[wordList[j]].push_back(wordList[i]);
}
}
}
}
//宽度优先搜索
int BFS_search(string &beginWord,string &endWord,
map<string,vector<string>> &graph,set<string> &visit){
queue<pair<string,int>> Q;
Q.push(make_pair(beginWord,1));
visit.insert(beginWord);
while(!Q.empty())
{
string str=Q.front().first;
int len=Q.front().second;
Q.pop();
for(auto temp:graph[str])
{
if(visit.find(temp)==visit.end())
{
Q.push(make_pair(temp,len+1));
visit.insert(temp);
if(temp==endWord)
return len+1;
}
}
}
return 0;
}
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
map<string,vector<string>> graph;
set<string> visit;
construct_graph(beginWord,wordList,graph);
return BFS_search(beginWord,endWord,graph,visit);
}
};
2.6 火柴拼正方形
Leetcode-473 火柴拼正方形
思路:递归加剪枝
class Solution {
public:
bool makesquare(vector<int>& matchsticks) {
//参考求子集的代码
if(matchsticks.size()<4)
return false;
int sum=accumulate(matchsticks.begin(),matchsticks.end(),0);
if(sum%4!=0)
return false;
int side[4]={0};//四条边
int target=sum/4;
sort(matchsticks.begin(),matchsticks.end(),[](int a,int b){return a>b;});
return generate(0,matchsticks,target,side);
}
//回溯思想
bool generate(int i,vector<int>& matchsticks,int &target,int side[])
{
if(i>=matchsticks.size())
return (side[0]==target && side[1]==target && side[2]==target && side[3]==target);
for(int j=0;j<4;j++)//在四个桶中分别尝试
{
if((side[j]+matchsticks[i])>target)
{
continue;
}
side[j]+=matchsticks[i];
if(generate(i+1,matchsticks,target,side))
{
return true;
}
side[j]-=matchsticks[i];
}
return false;
}
};