疯狂被积分虐,持续断更。—2018.12.30(雪)。
本来又懒得写东西了,但想想数据结构是以后常用的知识,还是努力努力吧。
为了能更加准确地表达表达概念,我将会从《挑战程序设计竞赛》中摘取部分内容用于博客描述。此外,以下所有图片均引用自《挑战程序设计竞赛》。
二叉搜索树性质与操作介绍
显而易见,二叉搜索树也是以树的形式来储存数据,但数据的排列方式有以下特点:对于所有的节点,左子节点上的数一定小于自己,右子节点上的数一定大于自己。所以,最小的数字一定在最左,而最大的数字在最右,且无相同数据。如图:
二叉搜索树的操作
(1)查询是否包括某一数值:如果大于当前节点就向右,反之向左走,等于就输出,如果不存在则最后返回空。
(2)插入一个数值:如上用查询的方法找到应该呆的位置,增加节点就好。
(3)删除一个数值:删除操作往往会比较困难,因为一旦删除,就会有子节点悬空,于是要提节点,此时又有好多情况:
1.需要删除的节点没有左儿子,就提右儿子。解释:右儿子>需删除节点>需删除节点的父亲,直接连接无问题。
2.需要删除的节点的左儿子没有右儿子,就提左儿子。解释:同上。
3.以上都不满足的话,就把左儿子的子孙中最大的节点提到需要删除的节点上。解释:为持续保持左小右大。
时间复杂度
O(logn)。每次都二分。
操作实现
//建立结构体
struct node
{
int val;//值
node *left,*right; //左右子节点
};
//插入x,返回地址
node *insert(node *p,int x)
{
if(*p==NULL)//空树时做
{
node *q=new node;
q->val=x;
q->left=q->right=NULL:
return q;
}
else
{
if(x<p->val) p->left=insert(p->left,x);//找
else p->right=insert(p->right,x);
return p;//除了最后一次加入的那个节点,其余返回的都是原有的地址值
}
}
//查询是否包括x
bool Find(node *p,int x)
{
if(p==NULL) return false;
else if(x==p->val) return true;
else if(x<p->val) return Find(p->left,x);
else return Find(p->right,x);
}
//删除x数值
node *remove(node *p,int x)
{
//找值
if(p==NULL) return NULL;
else if(x<p->val) p->left=remove(p->left,x);
else if(x>p->val) p->right=remove(p->right,x);
//第一种情况:没有左儿子
else if(p->left==NULL)
{
node *q=p->right;//提右儿子
delete p;//删
return q;//返回更新后的节点
}
//第二种情况:左儿子没有右儿子
else if(p->left->right==NULL)
{
node*q=p->left;
q->right=p->right;//提上去,原来的右儿子接上来
delete p;
return q;//返回更新后的节点
}
//其余情况
else
{
//p为要删除的点,r为左儿子子孙的最大节点,q为r的父节点
node *q;
for(q=p->left;q->right->right!=NULL;q=q->right);//左子孙中最大的节点一定是从右节点一直往下走
node *r=q->right;//r为左儿子子孙的最大节点
q->right=r->left;//r被拿走了,把r的左节点接到p的右边(p的左节点没动过,r没右节点)
r->left=p->left;//r继承p的左和右节点
r->right=p->right;
delete p;
return r;//返回更新后的节点
}
return p;//返回原有地址值,最后回到根
}
喜闻乐见的STL
set
关于迭代器的使用:set的各个函数的返回值都为指针(地址),所以一定要迭代器来接收返回值。
直接上实验代码,具体使用方式在代码中表示:
#include <iostream>
#include <set>
using namespace std;
int n,i,a,t;
int main()
{
set<int> s;
cout<<"请输入个数:";
cin>>n;
cout<<"请输入"<<n<<"个数字:";
//插入操作insert
for(i=0;i<n;i++)
{
cin>>a;
s.insert(a);
}
set<int>::iterator it;//迭代器
cout<<"排序后为:";
//首尾地址begin(),end()
for(it=s.begin();it!=s.end();it++)
cout<<*it<<" ";
//寻找find
cout<<endl<<"寻找:";
cin>>t;
it=s.find(t);
cout<<"find结果:";
if(it==s.end()) cout<<"Not found"<<endl;
else cout<<*it<<" "<<"Found"<<endl;
//计数count
cout<<"count结果:";
if(s.count(t)) cout<<"Found"<<endl;
else cout<<"Not Found"<<endl;
//长度size
cout<<"现有长度:"<<s.size()<<endl;
//删除erase
cout<<"消去"<<t<<"后长度:";
s.erase(t);
cout<<s.size()<<endl;
//清空clear
s.clear();
cout<<"清空后长度:"<<s.size()<<endl;
//空empty,已空则返回true,反之false
cout<<"结果:";
if(s.empty()) cout<<"已空"<<endl;
else cout<<"未空"<<endl;
return 0;
}
注意end()并不指向容器的任何元素,而是指向容器的最后元素的下一位置,称为超出末端迭代器。且此时end()不为NULL。
实测结果:
可知:set中依顺序是从小到大的,且不可有重复数据。
map
map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字key,每个关键字只能在map中出现一次,第二个可能称为该关键字的值value)的数据处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。
按我自己看别人的讲解,map好像是事先已知数据间的对应关系,在编程的时候直接输入,而不是运行时输入。map与set的不同之处在于map有key和value,key不可以重复,但value可以。
还是直接上代码:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
int t;
map<int,string>m;//键和值,key和value
//多种插入方式
m.insert(make_pair(1,"one"));
m.insert(make_pair(9,"nine"));
m.insert(pair<int,string>(3,"three"));
m.insert(pair<int,string>(6,"six"));
m.insert(map<int,string>::value_type(4,"four"));
m.insert(map<int,string>::value_type(5,"five"));
m[2]="two";//此种输入可以覆盖之前的值
m[7]="seven";
map<int,string>::iterator it;
cout<<"当前顺序:"<<endl;
for(it=m.begin();it!=m.end();it++)
cout<<it->first<<" "<<it->second<<endl;
//寻找find
cout<<"寻找:";
cin>>t;
it=m.find(t);
cout<<"find寻找结果:";
if(it==m.end()) cout<<"Not found"<<endl;
else cout<<it->first<<" "<<it->second<<" "<<"Found"<<endl;
//计数count
cout<<"count寻找结果:";
if(m.count(t)) cout<<"Found"<<endl;
else cout<<"Not found"<<endl;
//长度size
cout<<"当前长度:"<<m.size()<<endl;
//删去erase
m.erase(t);
cout<<"删去"<<t<<"后长度:"<<m.size()<<endl;
//清空clear
m.clear();
cout<<"清空后长度:"<<m.size()<<endl;
return 0;
}
实现效果:
由图可知:在不做任何干涉的情况下,map按key升序排列(数值升序,字符串字典序升序)。当然我们也可以对map的key降序排列:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
map<int,string,greater<int> >m;//greater<int>是降序,less<int>是升序
m.insert(make_pair(1,"one"));
m.insert(make_pair(9,"nine"));
m.insert(pair<int,string>(3,"three"));
m.insert(pair<int,string>(6,"six"));
m.insert(map<int,string>::value_type(4,"four"));
m.insert(map<int,string>::value_type(5,"five"));
m[2]="two";
m[7]="seven";
map<int,string>::iterator it;
cout<<"当前顺序:"<<endl;
for(it=m.begin();it!=m.end();it++)
cout<<it->first<<" "<<it->second<<endl;
return 0;
}
实现结果:
此外,我们也可以实现对value的升序与降序排列,但我有一点点菜,深感可能暂时用不到看不懂看不懂,就直接附上链接供参考:
c++stl中Map的按Key排序和按Value排序
(其实感觉把key和value反着输进去排序,再反着输出说不定也可以,value有重复的情况下还是算了吧)。
当然map还有好多可用的函数:
1.m1.swap(m2);可用于交换两个map;
2.it=m.lower_bound(3);返回键值>=给定元素的第一个位置。it=m.upper_bound(3);返回键值>给定元素的第一个位置。(注:在数组或vector中用到时要加始末位置,如it=lower_bound(a,a+n,1);或it=lower_bound(v.begin(),v.end(),1)。)在set里也可以用这个。这两个函数也可以用于判断某一值是否存在。if(*it==t)啥的……
3.以后再补,查到了暂时也不知道怎么用,还是不写了。
虽说有了总结与大致的理解,但我还还还还还没实际用过,总之,实践才是检验真理的唯一标准(雾),未来有了更多的理解会对此篇进行进一步的补充。