线性表
顺序表
#include <iostream>
using namespace std;
class List{
int *list;
int maxsize;
int size;
public:
List(){
maxsize=1000;
size=0;
list=new int[maxsize];
int len;
cin>>len;
for(int i=1;i<=len;i++){
cin>>list[i];
size++;
}
}
void insert(int pos,int key){
if(pos>size+1||pos<1){
cout<<"error"<<endl;
return;
}
for(int i=size;i>=pos;i--){
list[i+1]=list[i];
}
list[pos]=key;
size++;
display();
return;
}
void deleteKey(int pos){
if(pos>size||pos<1){
cout<<"error"<<endl;
return;
}
for(int i=pos;i<=size;i++){
list[i]=list[i+1];
}
size--;
display();
return ;
}
void searchKey(int pos){
if(pos>size||pos<1){
cout<<"error"<<endl;
return;
}
cout<<list[pos]<<endl;
}
void display(){
cout<<size<<" ";
for(int i=1;i<=size;i++){
cout<<list[i]<<" ";
}
cout<<endl;
}
void operation(){
display();
int pos,key;
cin>>pos>>key;
insert(pos,key);
cin>>pos>>key;
insert(pos,key);
cin>>pos;
deleteKey(pos);
cin>>pos;
deleteKey(pos);
cin>>pos;
searchKey(pos);
cin>>pos;
searchKey(pos);
}
};
int main()
{
List list;
list.operation();
return 0;
}
需要注意的地方:
- 下标从1开始(因此遍历是for(int i=1;i<=n;i++))
- 每插入或删除一个元素,size要改变
- 删除和查找的位置的异常位置是小于1或者大于size,但是插入的位置可以是size+1,(比如6个元素,可以在第7个位置插入)
- 由于判断是否出错,出错的话如果在函数内部输出,那么此时如果正确的话,链表信息展示也应该在函数内部输出
部分习题选取:
思路,先创建一个顺序表,用第一行的输入创建,然后第二行读取每个字符的时候,就插到对应位置上。
用a>list[i]&&a<list[i+1]来找到位置,如果到最后一个位置了就直接插入。
找到对应的位置后调用之前写好的insert函数进行插入。
链表
#include <iostream>
using namespace std;
class Node{
public:
int data;
Node *next;
Node(){next=NULL;}
};
class List{
Node *head;
int len;
public:
List(){
head=new Node;
cin>>len;
for(int i=0;i<len;i++){
int data;
cin>>data;
Node *p=new Node;
p->data=data;
Node *q=head;
while(q->next)q=q->next;
q->next=p;
}
}
void Operation(){
int pos,data;
cin>>pos>>data;
insertNode(pos,data);
cin>>pos>>data;
insertNode(pos,data);
cin>>pos;
deleteNode(pos);
cin>>pos;
deleteNode(pos);
cin>>data;
searchNode(data);
cin>>data;
searchNode(data);
}
void insertNode(int pos,int data){
len++;
Node *p=head;
if(pos>len+1||pos<1){
cout<<"error"<<endl;
return;
}
for(int i=0;i<pos-1;i++)p=p->next;
Node *q=new Node;
q->data=data;
q->next=p->next;
p->next=q;
display();
}
void deleteNode(int pos){
len--;
if(pos<1||pos>len){
cout<<"error"<<endl;
return ;
}
Node *p=head;
for(int i=0;i<pos-1;i++)p=p->next;
Node *q=p->next;
p->next=p->next->next;
delete q;
display();
}
void searchNode(int pos){
if(pos<1||pos>len){
cout<<"error"<<endl;
return ;
}
Node *p=head;
for(int i=0;i<pos;i++)p=p->next;
cout<<p->data<<endl;
}
void display(){
Node *p=head->next;
while(p){
cout<<p->data<<" ";
p=p->next;
}
cout<<endl;
}
};
int main()
{
List list1;
list1.display();
list1.Operation();
return 0;
}
需要注意的地方:
- 初始化时不要忘记结点需要这一条构造函数: Node(){next=NULL;}
- 创建链表时不要忘记初始化头结点head=new Node;
- 在display函数中,不能输出头结点的值,因为头结点不存储值:因此一开始初始化就应该Node *p=head->next;
- 对于插入来说,比如我要在第五个位置插入结点,那应该将结点移动到第四个结点,然后再新生成一个结点,让第四个结点指向它
- 对于删除来说,比如要删除第五个位置的结点,则需要移动到第四个结点,并且还额外需要一个结点来指向第五个结点,后面delete掉它
for(int i=0;i<pos-1;i++)p=p->next;
Node *q=p->next;
p->next=p->next->next; - 遍历链表的时候不要忘记p=p->next
- 插入和删除结点的时候不要忘记改变长度
- 插入或删除失败时会在函数内部输出error,因此成功时也应该在函数内部输出,或者要么统一在外面输出
习题:
思路:对于所有元素,我们都这样遍历链表,用q->next来遍历,如果q->next的后一个元素与该次遍历的元素相同,则删除q->next的元素,如果不相同则往后移
void L_del(){
ListNode *p,*q,*s;
p=head->next;
while(p){
q=p;
while(q->next){
if(q->next->data==p->data){ //当删除了一个结点后,q不往后移,下次重新判定
s=q->next;
q->next=q->next->next;
delete s;
len--;
}
else
q=q->next;
}
p=p->next;
}
}
堆栈
stack类使用的参考代码
- 包含头文件 : #include
- 创建一个堆栈对象s(注意stack是模板类):stack s; //堆栈的数据类型是字符型
- 把一个字符ct压入堆栈: s.push(ct);
- 把栈顶元素弹出:s.pop();
- 获取栈顶元素,放入变量c2: c2 = s.top();
- 判断堆栈是否空: s.empty(),如果为空则函数返回true,如果不空则返回false
习题:
算法流程
-
初始化,i=0,建立堆栈,栈为空
-
输入表达式,建立指针指向表达式的头部
-
读入表达式的第i个字符
-
如果第i个字符是左括号,入栈
-
如果第i个字符是右括号,检查栈顶元素是否匹配
A.如果匹配,弹出栈顶元素
B.如果不匹配,报错退出
-
i++,指向下一个字符,是否已经表达式末尾
A. 未到末尾,重复步骤3
B. 已到达末尾
a. 堆栈为空,输出ok b. 堆栈不为空,输出error
代码:
#include <iostream>
#include<stack>
using namespace std;
int main()
{
int t;
cin>>t;
while(t--){
int flag=1;
string a;
cin>>a;
stack<char> b;
for(int i=0;i<a.length();i++){
if(a[i]=='('||a[i]=='['||a[i]=='{'){
b.push(a[i]);
}
if(a[i]==')'||a[i]==']'||a[i]=='}'){
if(a[i]==')'){
if(b.top()=='(')b.pop();
else{
flag=0;
break;
}
}
else if(a[i]==']'){
if(b.top()=='[')b.pop();
else{
flag=0;
break;
}
}
else if(a[i]=='}'){
if(b.top()=='{')b.pop();
else{
flag=0;
break;
}
}
}
}
while(!b.empty()){
if(b.top()=='('||b.top()=='['||b.top()=='{'){
flag=0;
break;
}
b.pop();
}
if(flag==1)cout<<"ok"<<endl;
else cout<<"error"<<endl;
}
return 0;
}
思路,需要有两个栈,一个栈存放访问过的网站,一个栈存放因为BACK,然后需要将这些网站放到另一个栈中。
此时如果访问新的网站,则需要将第二个栈的内容全部清空。
#include <iostream>
#include<stack>>
using namespace std;
int main()
{
stack<string> a;
stack<string> c;
a.push("http://www.acm.org/");
while(1){
string b;
cin>>b;
if(b[0]=='V'){
string web;
cin>>web;
a.push(web);
cout<<a.top()<<endl;
while(!c.empty())c.pop();
}
else if(b[0]=='B'){
if(a.empty())cout<<"Ignored"<<endl;
else {
string now=a.top();
a.pop();
if(a.empty()){
cout<<"Ignored"<<endl;
a.push(now);
continue;
}
c.push(now);
cout<<a.top()<<endl;
}
}
else if(b[0]=='F'){
if(c.empty()){
cout<<"Ignored"<<endl;
continue;
}
else{
cout<<c.top()<<endl;
a.push(c.top());
c.pop();
}
}
else if(b[0]=='Q'){
break;
}
}
}
说实话这题有点难,不知道会不会考这种类型的,先暂时不复习了
#include<bits/stdc++.h>
using namespace std;
#define OK 0
#define ERROR -1
#define OVERFLOw -1
#define OPSETSIZE 7 //运算符号集合长度,目前只有7个符号
typedef int status;
char OPSET[7]={'+','-','*','/','(',')','#'};//运算符集合
bool In(char Test,char* TestOp){//判断字符Test是否是运算符
for(int i=0;i<7;i++)
if(Test==TestOp[i])
return true;
return false;
}
char Prior[7][7]={{'>','>','<','<','<','>','>'},{'>','>','<','<','<','>','>'},
{'>','>','>','>','<','>','>'},{'>','>','>','>','<','>','>'},{'<','<','<','<','<','=',' '},
{'>','>','>','>',' ','>','>'},{'<','<','<','<','<',' ','='}
};
char precede(char Aop,char Bop){
int x,y;
for(int i=0;i<7;i++){
if(Aop==OPSET[i])x=i;
}
for(int i=0;i<7;i++){
if(Bop==OPSET[i])y=i;
}
return Prior[x][y];
}
float Operate(float a,unsigned char theta,float b){
float result;
if(theta=='+')result=a+b;
if(theta=='-')result=a-b;
if(theta=='*')result=a*b;
if(theta=='/')result=a/b;
return result;
}
float EvaluateExpression(string MyExp){
//算术表达式求值的算符优先算法。
//设OPTR和OPHD芬别为运算符转和运算数栈,_oP为运算符集合。
stack<char> OPTR;//运算符栈,字符元素_
stack <double> OPND;//运算数栈,实数光素
char TempData[20];
double Data,a,b,r;
char theta,Dr[2];
char c;
int i=0;//用于控制字符串的位置移动
//InitStack (OPTR); InitStack(OPND);两行代码无用去掉
//Push(OPTR,'#');C =MuExpression;原有两行代码改造为下面两句
OPTR.push('#');
c=MyExp[0];
strcpy(TempData,"\0");
while(c!='#'||OPTR.top()!='#'){
if(!In(c,OPSET)){
Dr[0]=c;
Dr[1]='\0';
strcat(TempData,Dr);
c=MyExp[++i];//读下——个字符
if(In(c,OPSET)) {
Data=(float)atof(TempData);
OPND.push(Data);
//l---------致造上—句代码为—句c++代码
strcpy(TempData,"\0");
}//c是运算符,表明前面读入了一个完整操作数
} //读入的字符不是运算符,是1个数字
else {//是运算符,开始进行计算
switch (precede(OPTR.top(),c)){
case '<'://栈顶元素优先校低
OPTR.push(c);
//i ---------攻造上—包伐码为一句c++代码
i++;
c=MyExp[i];
//il..-------读下一个字符
break;
case '='://脱括号并接收下一字符
OPTR.pop();
// ---------改造上一包伐码为1-2句C++代码
i++;
c=MyExp[i];
//, ---------读下—个字符
break;
case '>'://退栈并将运算结果入栈
theta=OPTR.top();
OPTR.pop();//Pop(OPTR,theta);
b=OPND.top();
OPND.pop(); //Pop(OPND,b);
a=OPND.top();
OPND.pop(); //Pop(OPND, a)
//Push(OPND,Operate(a,theta,b)
OPND.push(Operate(a,theta,b));
// ---------把上面四句代码改造成7-8司c++代码
break;
}
}
}
return OPND.top();
//---------改造上—司代码为一句c++代码
// end function
}
int main(){
string Exp;
int t;
double result;
cin>>t;
while (t--) {
cin>>Exp;
result = EvaluateExpression(Exp);
cout<<fixed<<setprecision(4)<<result<<endl;
}
return 0;
}
样例输入
2
8
0 0 0 1 1 1 1 1
1 0 0 0 1 0 0 1
1 0 0 0 1 0 0 0
1 1 0 0 0 0 0 1
0 0 1 1 0 1 1 0
0 0 0 0 0 0 1 1
1 1 1 1 1 0 0 1
0 0 0 0 1 0 0 0
7
0 0 0 1 1 1 1
1 0 0 1 0 0 1
1 0 0 1 0 0 0
1 1 0 0 0 0 1
0 0 1 1 0 1 0
1 0 0 0 0 1 0
0 0 0 0 1 1 0
样例输出
[0,0]–[0,1]–[0,2]–[1,2]–
[1,3]–[2,3]–[3,3]–[3,4]–
[4,4]–[5,4]–[5,5]–[6,5]–
[6,6]–[7,6]–[7,7]–END
no path
队列
队列queue的用法如下:
- 包含头文件:#include
- 定义一个整数队列对象:queue myQe;
- 定义一个整数队列对象数组:queue myQe[10];
- 入队操作:myQe.push(itemp); //把整数itemp进入队列
- 出队操作:myQe.pop(); //把队头元素弹出队列,注意本操作不获取队头元素
- 获取队头元素: itemp = myQe.front(); // 把队头元素放入itemp中,注意本操作不弹出元素
- 判断队列是否为空:myQe.empty();//队列空则返回true,不空则返回false
#include <iostream>
#include<queue>
using namespace std;
int main()
{
int num;
cin>>num;
queue<int> A;
queue<int> B;
for(int i=0;i<num;i++){
int account;
cin>>account;
if(account%2==1){
A.push(account);
}
if(account%2==0){
B.push(account);
}
}
while(!A.empty()&&!B.empty()){
if(!A.empty()){
cout<<A.front()<<" ";
A.pop();
}
if(!A.empty()){
cout<<A.front()<<" ";
A.pop();
}
if(!B.empty()){
cout<<B.front()<<" ";
B.pop();
}
}
while(!A.empty()){
cout<<A.front()<<" ";
A.pop();
}
while(!B.empty()){
cout<<B.front()<<" ";
B.pop();
}
return 0;
}
样例输入
2
19.125 2
15.125 16
样例输出
10011.001
F.200
#include <iostream>
#include<stack>
#include<queue>
using namespace std;
int main()
{
int t;
cin>>t;
while(t--){
stack<char> zs;
queue<char> xs;
double num;
int k;
cin>>num>>k;
int zs1=num;
double xs1=num-zs1;
//整数部分 zs:整数 xs:小数
while(1){
int ys=zs1%k;
char ys1;
if(ys>9){
ys1=ys-10+'A';
}
else ys1=ys+'0';
int s=zs1/k;
zs1=s;
zs.push(ys1);
if(zs1==0)break;
}
while(!zs.empty()){
cout<<zs.top();
zs.pop();
}
cout<<".";
//小数部分
int i=0;
while(1){
double j=xs1*k;//积
int ys=(int)j;
char ys1;//余数1
if(ys>9){
ys1=ys-10+'A';
}
else ys1=ys+'0';
xs1=j-int(j);
//cout<<j<<endl;
xs.push(ys1);
i++;
if(xs1==0)break;
}
while(i<3){
xs.push('0');
i++;
}
while(!xs.empty()){
cout<<xs.front();
xs.pop();
}
cout<<endl;
}
return 0;
}
样例输入
9
0 20
1 15
1 61
2 10
10 5
10 3
30 18
31 25
31 2
3
样例输出
6.2 17 62
#include <iostream>
#include<queue>
#include<iomanip>
using namespace std;
int main()
{
queue<int> a,b,c;
int count;
cin>>count;
int num=0;
int waittime[count];
int ddtime[count],cltime[count];
int nowtime=0;
for(int i=0;i<count;i++){
cin>>ddtime[i]>>cltime[i];
}
int k;
cin>>k;
while(1){
/*if(a.empty()||b.empty()||c.empty()){
cout<<"nowtime:"<<nowtime<<endl;
count--;
if(count==-1)break;
}*/
//cout<<nowtime<<" ";
if(a.empty()&&nowtime>=ddtime[num]&&num<count){
a.push(cltime[num]);
waittime[num]=nowtime-ddtime[num];
num++;
}
if(b.empty()&&nowtime>=ddtime[num]&&num<count){
b.push(cltime[num]);
waittime[num]=nowtime-ddtime[num];
num++;
}
if(c.empty()&&nowtime>=ddtime[num]&&num<count){
waittime[num]=nowtime-ddtime[num];
c.push(cltime[num]);
num++;
}
if(num==count&&(a.empty()&&b.empty()&&c.empty()))break;
if(!a.empty()){
int atime=a.front();
//cout<<"A:"<<atime<<" ";
a.pop();
atime--;
if(atime!=0)a.push(atime);
else{
if(a.empty()&&nowtime>=ddtime[num]&&num<count){
a.push(cltime[num]);
waittime[num]=nowtime-ddtime[num]+1;
num++;
}
}
}
if(!b.empty()){
int btime=b.front();
//cout<<"B:"<<btime<<" ";
b.pop();
btime--;
if(btime!=0)b.push(btime);
else{
if(b.empty()&&nowtime>=ddtime[num]&&num<count){
b.push(cltime[num]);
waittime[num]=nowtime-ddtime[num]+1;
num++;
}
}
}
if(!c.empty()){
int ctime=c.front();
//cout<<"C:"<<ctime<<" ";
c.pop();
ctime--;
if(ctime!=0)c.push(ctime);
else{
if(c.empty()&&nowtime>=ddtime[num]&&num<count){
c.push(cltime[num]);
waittime[num]=nowtime-ddtime[num]+1;
num++;
}
}
}
//cout<<endl;
nowtime++;
}
double waittimesum=0;
int maxwaittime=0;
for(int i=0;i<count;i++){
//cout<<"wait"<<i+1<<":"<<waittime[i]<<endl;
//waittime[i]+=1;
if(waittime[i]>maxwaittime)maxwaittime=waittime[i];
waittimesum+=waittime[i];
}
waittimesum/=count;
cout<<fixed<<setprecision(1)<<waittimesum<<" ";
cout<<maxwaittime<<" "<<nowtime;
return 0;
}
串
样例输入
3
qwertyuiop
tyu
aabbccdd
ccc
aaaabababac
abac
样例输出
-1 0 0
5
-1 0 1
0
-1 0 0 1
8
#include<iostream>
#include<string>
using namespace std;
class myString {
string mainstr;
public:
void GetNext(string p, int next[]){
int lenp=p.length();
int k=-1;
int j=0;
next[0]=-1;
while(j<lenp-1){
if(k==-1||p[k]==p[j]){
k++,j++;
next[j]=k;
}
else k=next[k];
}
}
void KMPFind(string p, int pos){
int i=pos,j=0;
int lenm=mainstr.length(),lenp=p.length();
int *next=new int[lenp];
GetNext(p,next);
for(int k=0;k<lenp;k++){
cout<<next[k]<<" ";
}
cout<<endl;
int result;
while(i<lenm&&j<lenp){
if(j==-1||mainstr[i]==p[j]){
i++,j++;
}
else j=next[j];
}
if(j>=lenp){
result=i-lenp;
}
else result=-1;
cout<<result+1<<endl;//result为0代表找不到的意思
}
myString(string a):mainstr(a){}
~myString(){
mainstr = "";
}
};
int main() {
int t;
cin >> t;//测试t次
while (t--){
string a,b;
cin>>a>>b;
myString c(a);
c.KMPFind(b,0);
}
return 0;
}
代码的详细解析请看我的另一篇文章
KMP算法
习题:
一、利用kmp算法,取next数组中的最大值
#include <iostream>
#include <string>
using namespace std;
int GetMax(string s){
int i, j,len=s.length();
int *next=new int[len+1]; //注意:多求一次,即计算next[len],不然没法计算最后一个字符是重合的情况
int max=-2;
for(int k=0;k<len-1;k++){ //运行len-1次,每次从前面减少一个字符
string p=s.substr(k); //获取本次需计算next值的字符串,子串是从位置k开始到字符串尾
next[0] = -1; j=0; i=-1;
while (j < len ){
if (i == -1 || p[j] == p[i]){
i++; j++; next[j] = i;
if(i>max)
max=i;
}
else
i = next[i];
}
}
int result=max;
if(max==len-1)//若整个字符串由单个字符组成
result=len/2;
else if(max>=len/2) //若有重叠部分
result=len-max;
else if(max==0) //若没有重复子串
result=-1;
delete[] next;
return result;
}
int main(){
int t;
string s;
cin>>t;
while(t--){
cin>>s;
cout<<GetMax(s)<<endl;
}
return 0;
}
因为如果长度大于一半,比如说总长度为8,kmp算出的最大next数组的值为5,那么中间肯定有重叠的部分
ABCABCAB
此时最大子串为总长度减去8-5=3
else if(max>=len/2) //若有重叠部分
result=len-max;
样例输入
3
aaa
abca
abcdefg
样例输出
0
2
7
需要注意一点,我们就是首先要找到最小循环节,然后把整个串补充成最小循环节的倍数。
还有就是一开始要给它加一个特别的字符,这样方便把最后一个字符也能算出它的next的值
#include<iostream>
#include<string>
using namespace std;
class myString {
string mainstr;
public:
void GetNext(string p, int next[]){
int lenp=p.length();
int k=-1;
int j=0;
next[0]=-1;
while(j<lenp-1){
if(k==-1||p[k]==p[j]){
k++,j++;
next[j]=k;
}
else k=next[k];
}
}
void KMPFind(string p, int pos){
int i=pos,j=0;
int lenm=mainstr.length(),lenp=p.length();
int *next=new int[lenp];
GetNext(p,next);
for(int k=0;k<lenp;k++){
cout<<next[k]<<" ";
}
cout<<endl;
int result;
while(i<lenm&&j<lenp){
if(j==-1||mainstr[i]==p[j]){
i++,j++;
}
else j=next[j];
}
if(j>=lenp){
result=i-lenp;
}
else result=-1;
cout<<result+1<<endl;
}
myString(string a):mainstr(a){}
~myString(){
mainstr = "";
}
};
int main() {
int t;
cin >> t;
while (t--){
string a;
cin>>a;
a+='?';
myString b(a);
int *next=new int[a.length()];
b.GetNext(a,next);
if(next[a.length()-1]==0){
cout<<a.length()-1<<endl;
continue;
}
int circlelen=a.length()-1-next[a.length()-1];
int uplen=circlelen-(a.length()-1)%circlelen;
if((a.length()-1)%circlelen==0)
uplen=0;
cout<<uplen<<endl;
}
return 0;
}
而最小循环节就是用总长度减去next数组里的最后一个的值:
int circlelen=a.length()-1-next[a.length()-1];
比如ABCA
循环节算出来是3
那么此时还需要补充多少呢
那得看最后一个循环节出现了几个
(a.length()-1)-circlelen也就是1 (A)
所以还需要补充BC形成ABC
但是如果是ABCABCA
这样的话,就不能简单的去减了
而是应该取余
应该用
(a.length()-1)%circlelen得到1
然后用3-1得到2
但是有一种特殊情况需要考虑,例如abcabcabc
此时循环节长度为3
(a.length()-1)%circlelen=0
uplen=circlelen-(a.length()-1)%circlelen=3
算出来uplen=3,这样就不对
所以对于这种情况需要特殊考虑:
if((a.length()-1)%circlelen==0)
uplen=0;
树
基本概念
二叉树性质:
遍历二叉树
void PreOrderTraverse (BiNode* T ) {
if (T) {
cout << T->data;
PreOrderTraverse ( T->lChild );
PreOrderTraverse ( T->rChild );
}
}
中序遍历
void InOrderTraverse (BiNode* T ) {
if (T) {
InOrderTraverse ( T->lChild );
cout << T->data;
InOrderTraverse ( T->rChild );
}
}
void PostOrderTraverse (BiNode* T ) {
if (T) {
PostOrderTraverse ( T->lChild );
PostOrderTraverse ( T->rChild );
cout << T->data;
}
}
样例输入
2
AB#C##D##
AB##C##
样例输出
ABCD
BCAD
CBDA
ABC
BAC
BCA
#include <iostream>
using namespace std;
class treenode{
public:
char data;
treenode *left,*right;
treenode(){
left=NULL,right=NULL;
}
};
class tree{
treenode *root;
public:
tree(){
root=cretabitree();
}
treenode* createbitree(){
treenode *t;//一定要先创建一个结点,然后为其赋值
char a;
cin>>a;
if(a!='#'){
t=new treenode();
t->data=a;
t->left=createbitree();
t->right=createbitree();
}
else t=NULL;
return t;
}
void preorder1(treenode *t){
if(t!=NULL){
cout<<t->data;
preorder1(t->left);
preorder1(t->right);
}
else return;
}
void inorder1(treenode *t){
if(t!=NULL){
inorder1(t->left);
cout<<t->data;
inorder1(t->right);
}
else return;
}
void postorder1(treenode *t){
if(t!=NULL){
postorder1(t->left);
postorder1(t->right);
cout<<t->data;
}
else return;
}
void preorder(){
preorder1(root);
}
void inorder(){
inorder1(root);
}
void postorder(){
postorder1(root);
}
};
int main()
{
int t;
cin>>t;
tree tree0;
while(t--){
tree0.createtree();
tree0.preorder();
cout<<endl;
tree0.inorder();
cout<<endl;
tree0.postorder();
cout<<endl;
}
return 0;
}
建树的错误写法:
Tree(){
root=new Node;
createTree(root);
}
void createTree(Node *node){
char data;
cin>>data;
if(data!='#'){
node->data=data;
createTree(node->left);
createTree(node->right);
}
else node=null;
}
不是把结点传进去,而是构造结点,然后将这个结点赋值给左右子树,因此建树的函数返回值应该是结点,不需要参数
正确写法:
tree(){
root=cretabitree();
}
treenode* createbitree(){
treenode *t;//一定要先创建一个结点,然后为其赋值
char a;
cin>>a;
if(a!='#'){
t=new treenode();
t->data=a;
t->left=createbitree();
t->right=createbitree();
}
else t=NULL;
return t;
}
原来的右边孩子消失,右边的兄弟(只包括亲兄弟不包括堂表弟)全部变成右孩子,左孩子不变
二叉树的顺序存储
样例输入
3
3 1 2 3
5 1 2 3 0 4
13 1 2 3 4 0 5 6 7 8 0 0 9 10
样例输出
1 2 3
1 2 4 3
1 2 4 7 8 3 5 9 10 6
提示
注意从数组位置和二叉树深度、结点位置进行关联,或者父子结点在数组中的位置存在某种管理,例如i, i+1, i/2, i+1/2…或者2i, 2i+1…仔细观察哦
#include <iostream>
using namespace std;
void preorder(int index,int tree[],int len){
if(tree[index]!=0)cout<<tree[index]<<" ";
if(index*2<=len)preorder(index*2,tree,len);
if(index*2+1<=len)preorder(index*2+1,tree,len);
return;
}
int main()
{
int t;
cin>>t;
while(t--){
int len;
cin>>len;
int *tree=new int[len+1];
for(int i=1;i<=len;i++){
cin>>tree[i];
}
preorder(1,tree,len);
cout<<endl;
}
return 0;
}
二叉树的链式存储
样例输入
3
AB0C00D00
AB00C00
ABC00D00E00
样例输出
2
2
3
只需要在链式的遍历时:
加一条这样的语句
如果要判断其是不是左叶子结点
样例输入
3
AB0C00D00
AB00C00
ABCD0000EF000
样例输出
C D
B A
B C
A A
D F
C E
void PreOrder(Node *node){
if(node){
//cout<<node->data;
if(node->left==NULL&&node->right==NULL){
cout<<node->data<<" ";
}
PreOrder(node->left);
PreOrder(node->right);
}
else return;
}
void PreOrder2(Node *node){
if(node){
//cout<<node->data;
if(node->left!=NULL){
if(node->left->left==NULL&&node->left->right==NULL){
cout<<node->data<<" ";
}
}
if(node->right!=NULL){
if(node->right->left==NULL&&node->right->right==NULL){
cout<<node->data<<" ";
}
}
PreOrder2(node->left);
PreOrder2(node->right);
}
else return;
}
方法一:采用递归的做法,很巧妙的思维:
int getHeight(treenode *root){
if(root==NULL)return 0;
int left=getHeight(root->left);
int right=getHeight(root->right);
if(left>right)return left+1;
else return right+1;
}
简单的来说就是我要求自己这颗子树的高度,我就交给我的孩子去求,先求左孩子再求右孩子,一层一层递归到最底层。
我算出了我的子树的高度之后呢,那么我自己的高度就是左右两颗子树中最大的那一个加一。
方法二:
设定一个最大值,然后每次往下遍历的时候,如果高度大于最大值,则更新最大值:
void PreOrder(Node *node,int h){
if(node){
if(h+1>height)height=h+1;
PreOrder(node->left,h+1);
PreOrder(node->right,h+1);
}
else return;
}
样例输入
2
AB0C00D00
4 5 3 2 6
ABCD00E000FG00H0I00
9 5 4 11 7 2 8 13 4 1
样例输出
11
27
方法一:
采用和上面类似的方法:
int GetMaxRoad(TreeNode *root){
if(root==NULL)return 0;
int left=GetMaxRoad(root->left);
int right=GetMaxRoad(root->right);
if(left>right)return root->w+left;
else return root->w+right;
}
方法二:
在类里面多设一个变量Max,
然后,在遍历的时候,如果加上子结点的权值的时候其值大于Max,则更新Max的值:
void pre(treenode *t,int w){
if(t){
if(t->w+w>Max)Max=t->w+w;
pre(t->left,t->w+w);
pre(t->right,t->w+w);
}
else return;
}
样例输入
3
ABCDE
ABD##E##C##
ABC##DE####W##F
AB##CDW###E#F##
abc##d
ab##c#d##
样例输出
YES
YES
NO
只需要判断这两种的先序和中序的结果相不相同就可以了,因为先序和中序可以唯一确定一颗二叉树
#include <iostream>
using namespace std;
class treenode{
public:
char data;
treenode *left,*right;
treenode(){
left=NULL,right=NULL;
}
};
class tree{
treenode *root;
string pre,in,post;
public:
tree(){}
void createtree(){
root=createbitree();
}
treenode* createbitree(){
treenode *t;
char a;
cin>>a;
if(a!='#'){
t=new treenode();
t->data=a;
t->left=createbitree();
t->right=createbitree();
}
else t=NULL;
return t;
}
void preorder1(treenode *t){
if(t!=NULL){
pre+=t->data;
preorder1(t->left);
preorder1(t->right);
}
else return;
}
void inorder1(treenode *t){
if(t!=NULL){
inorder1(t->left);
in+=t->data;
inorder1(t->right);
}
else return;
}
void postorder1(treenode *t){
if(t!=NULL){
postorder1(t->left);
postorder1(t->right);
post+=t->data;
}
else return;
}
void preorder(){
preorder1(root);
}
void inorder(){
inorder1(root);
}
void postorder(){
postorder1(root);
}
string getPre(){return pre;}
string getIn(){return in;}
string getPost(){return post;}
};
void arrayPreOrder(string arrayTree,int i,string &PreOrderTree){
if(i>=arrayTree.length())return;
if(arrayTree[i]!='#')PreOrderTree+=arrayTree[i];
arrayPreOrder(arrayTree,2*i+1,PreOrderTree);
arrayPreOrder(arrayTree,2*i+2,PreOrderTree);
}
void arrayInOrder(string arrayTree,int i,string &InOrderTree){
if(i>=arrayTree.length())return;
arrayInOrder(arrayTree,2*i+1,InOrderTree);
if(arrayTree[i]!='#')InOrderTree+=arrayTree[i];
arrayInOrder(arrayTree,2*i+2,InOrderTree);
}
void arrayPostOrder(string arrayTree,int i,string &PostOrderTree){
if(i>=arrayTree.length())return;
arrayPostOrder(arrayTree,2*i+1,PostOrderTree);
arrayPostOrder(arrayTree,2*i+2,PostOrderTree);
if(arrayTree[i]!='#')PostOrderTree+=arrayTree[i];
}
int main()
{
int t;
cin>>t;
while(t--){
string arrayTree;
cin>>arrayTree;
tree tree0;
tree0.createtree();
tree0.preorder();
tree0.inorder();
tree0.postorder();
string pretree,intree,posttree;
pretree=tree0.getPre();
intree=tree0.getIn();
posttree=tree0.getPost();
string arrayPre,arrayIn,arrayPost;
arrayPreOrder(arrayTree,0,arrayPre);
arrayInOrder(arrayTree,0,arrayIn);
arrayPostOrder(arrayTree,0,arrayPost);
//cout<<arrayPre<<endl<<arrayIn<<endl<<arrayPost<<endl;
if(arrayPre==pretree&&intree==arrayIn&&posttree==arrayPost)cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}
二叉树非递归遍历
这个之后再来补充吧
先附上大佬的博客链接
https://blog.csdn.net/zhangxiangdavaid/article/details/37115355
二叉树的层次遍历
Huffman树
这里还需要注意一点,对于兄弟之间,一般来说,在左边的树的权值是会小一点的。看下图可以得知。
事实上如果我们没有限制在兄弟中左边小于右边,则画出的树不唯一。
习惯上我们限制左边小于右边
再来一道画huffman树的例题:
首先附上题目和自己书写的过程:
在画的时候遇到了一些问题:
当画到这里的时候,我们并不清楚是应该将448这棵子树放在第一层右边还是第二层左边?
这个时候我们就先不要把448放进这两个位置,先放一边,然后继续往下画,把8当成是那个最小的加入数组中最小两个的比较范围中。
接下来继续往后,此时便可以确定448应该放在哪里了
再看下面这个例子 ,在两种情况下,336这个子树被放在了不同的地方
所以总结就是,对于这种未知的情况,可以先将其放在一边,继续往后写,然后再确定其位置(当然也可以先正常无脑画完,画完后再重新画一次来调整)
代码实现:
在设计Huffman树的结点结构时,考虑到在寻求Huffman编码的过程中,需要多次从叶子结点向根结点回溯,因此采用三叉链表结构。
假设叶子结点数为n,从Huffman算法可知(哈夫曼树不应存在度为1的结点,分支结点都是度为2的结点,因为路径要最短)。
Huffman树中的度为0的结点数是n,因此度为2的分支结点数是n-1。
由此可见,Huffman树的存储空间是可以预见的,为2n-1。通常,数组的第一个单元浪费不用,下标从1~2n-1共2n-1个元素。因此分配的空间是2n个
class HuffmanTree{
TreeNode *treenode;
int num;
public:
HuffmanTree(int num1){
num=num1;
treenode=new TreeNode[2*num];
for(int i=0;i<2*num;i++){
treenode[i].parent=0;
}
for(int i=1;i<=num;i++){
cin>>treenode[i].data;
}
for(int i=1;i<=num;i++){
cin>>treenode[i].w;
}
}
void selectMin(int len,int &p1,int &p2){
int Max=9999;
int min1=Max,min2=Max;
//p1=0,p2=0;
for(int i=1;i<len;i++){
if(treenode[i].parent==0){
if(treenode[i].w<min1){
min2=min1;
p2=p1;
min1=treenode[i].w;
p1=i;
}
else if(treenode[i].w<min2){
min2=treenode[i].w;
p2=i;
}
}
}
}
void createHuffmanTree(){
for(int i=num+1;i<2*num;i++){
int p1=0,p2=0;
selectMin(i,p1,p2);
treenode[i].left=p1;
treenode[i].right=p2;
treenode[p1].parent=i;
treenode[p2].parent=i;
treenode[i].w=treenode[p1].w+treenode[p2].w;
}
}
};
需要注意的地方,以及自己犯错的地方:
在建树的时候,遍历是需要对从1到2*num-1进行遍历的
这是因为选取两个最小的加入后,此时我们也需要在父节点中选取最小的进行计算。
并且还不能带等号
并且p1和p2每次都得重新选取,所以应该设置成放在内部的局部变量:
注意这三个地方,由于我们每次需要选取最小的,但是开始的时候,num+1-2*num-1这些都是父节点,这些父节点的权值都还是0,那么我们从结点中寻找最小值的时候就不能将其包括进去。
另一方面,我们可以找寻最小值的时候,是不包括len本身的,此处是小于号。
还需要注意一点的是,我们建树,其实要做的是:
- 找到权值最小的两个结点,然后从父节点的第一个位置,也就是num+1开始,我们修改其权值,让它等于子结点两个权值之和
- 然后还要修改其左右孩子结点为找到的最小的两个的下标。
- 修改孩子的父亲为该父亲。
- 需要注意的一点是为了是最终的代码唯一,我们限制死了在兄弟中,小的固定在左子树。
数据结构还有笔试题,在笔试题中我们最好也限制小的在左边而大的在右边,这是为了保证和代码的结果统一。
(虽然说不加这限制也可以,那么就会导致结果不统一。)
因此这里正确的写法为:
编码
需要注意的一点是,我们看图来给字符编码是从根节点到叶子结点,但是编程则是从叶子结点往根走。但是这里的叶子结点全部存储在1-n(包括n)的地方。而父结点全部存储在n+1到2n-1的地方,并且这些结点全部都不含有字符信息,字符全部都在叶子节点上。
注意,这里的编码是指给字符编码,还有另外一种是给你一串字符串,让你加密成数字的情况。
void HuffmanCode(){
for(int i=1;i<=num;i++){
string code="";
for(int j=i;treenode[j].parent!=0;j=treenode[j].parent){
if(treenode[treenode[j].parent].left==j)code+='0';
else code+='1';
}
for(int k=code.length()-1;k>=0;k--){
treenode[i].code+=code[k];
}
}
}
注意:这里循环不要写错了
下面红框的循环应该在上面红框的循环之外。
译码:给一段字符串,然后让你将其转化成字符
void deCode(string str){
string stringCode="";
int root=2*num-1;
int j=root;
int outflag=0;
for(int i=0;i<str.length();i++){
outflag=0;
if(str[i]=='0'){
j=treenode[j].left;
}
else if(str[i]=='1'){
j=treenode[j].right;
}
else{
cout<<"error"<<endl;
break;
}
if(treenode[j].left==0&&treenode[j].right==0){
outflag=1;
//outflag代表以及读完了一个字符的编码,此时如果走到了字符串的尽头,则结束循环时就不会判定为错误。但是如果不在读完一个字符的编码时,这时候结束循环,此时outflag会被判定为0,代表不该出去,此时就是出现了字符冗余的情况。
stringCode+=treenode[j].data;
j=root;
//如果找到一个字符,下次再找的时候应该还是从头开始找
}
}
if(outflag==1){
cout<<stringCode<<endl;
}
else cout<<"error"<<endl;
}
例如
对特定字符串进行加密:
(例如给定ABC让你编码成01的字符串)
void StringCode(string str){
string stringcode="";
for(int i=0;i<str.length();i++){
for(int j=1;j<=num;j++){
if(str[i]==treenode[j].data){
stringcode+=treenode[j].code;
break;
}
}
}
cout<<stringcode<<endl;
}
需要注意一下,内外循环的i不应该相同,
例题:
样例输入
2
5 A B C D E
15 4 4 3 2
ABDEC
00000101100
4 A B C D
7 5 2 4
ABAD
1110110
样例输出
A :1
B :010
C :011
D :001
E :000
1010001000011
error!
A :0
B :10
C :110
D :111
0100111
DAC
完整代码:
#include <iostream>
using namespace std;
class TreeNode{
public:
int parent,left,right;
int w;
string code;
char data;
Treenode(){
parent=0,left=0,right=0;
code="";
}
};
class HuffmanTree{
TreeNode *treenode;
int num;
public:
HuffmanTree(int num1){
num=num1;
treenode=new TreeNode[2*num];
for(int i=0;i<2*num;i++){
treenode[i].parent=0;
}
for(int i=1;i<=num;i++){
cin>>treenode[i].data;
}
for(int i=1;i<=num;i++){
cin>>treenode[i].w;
}
}
void selectMin(int len,int &p1,int &p2){
int Max=9999;
int min1=Max,min2=Max;
//p1=0,p2=0;
for(int i=1;i<len;i++){
if(treenode[i].parent==0){
if(treenode[i].w<min1){
min2=min1;
p2=p1;
min1=treenode[i].w;
p1=i;
}
else if(treenode[i].w<min2){
min2=treenode[i].w;
p2=i;
}
}
}
}
void createHuffmanTree(){
for(int i=num+1;i<2*num;i++){
int p1=0,p2=0;
selectMin(i,p1,p2);
treenode[i].left=p1;
treenode[i].right=p2;
treenode[p1].parent=i;
treenode[p2].parent=i;
treenode[i].w=treenode[p1].w+treenode[p2].w;
}
}
void HuffmanCode(){
for(int i=1;i<=num;i++){
string code="";
for(int j=i;treenode[j].parent!=0;j=treenode[j].parent){
if(treenode[treenode[j].parent].left==j)code+='0';
else code+='1';
}
for(int k=code.length()-1;k>=0;k--){
treenode[i].code+=code[k];
}
}
}
void deCode(string str){
string stringCode="";
int root=2*num-1;
int j=root;
int outflag=0;
for(int i=0;i<str.length();i++){
outflag=0;
if(str[i]=='0'){
j=treenode[j].left;
}
else if(str[i]=='1'){
j=treenode[j].right;
}
else{
cout<<"error"<<endl;
break;
}
if(treenode[j].left==0&&treenode[j].right==0){
outflag=1;
//outflag代表以及读完了一个字符的编码,此时如果走到了字符串的尽头,则结束循环时就不会判定为错误。但是如果不在读完一个字符的编码时,这时候结束循环,此时outflag会被判定为0,代表不该出去,此时就是出现了字符冗余的情况。
stringCode+=treenode[j].data;
j=root;
//如果找到一个字符,下次再找的时候应该还是从头开始找
}
}
if(outflag==1){
cout<<stringCode<<endl;
}
else cout<<"error"<<endl;
}
void StringCode(string str){
string stringcode="";
for(int i=0;i<str.length();i++){
for(int j=1;j<=num;j++){
if(str[i]==treenode[j].data){
stringcode+=treenode[j].code;
break;
}
}
}
cout<<stringcode<<endl;
}
void display(){
for(int i=1;i<=num;i++){
cout<<treenode[i].data<<" :"<<treenode[i].code<<endl;
}
}
};
int main()
{
int t;
cin>>t;
while(t--){
int num;
cin>>num;
HuffmanTree tree(num);
tree.createHuffmanTree();
tree.HuffmanCode();
tree.display();
string code;
cin>>code;
tree.StringCode(code);
string decode;
cin>>decode;
tree.deCode(decode);
}
return 0;
}
样例输入
2
xA00tB00zC00D00
4 7 6 2 3
ab0C00D00
2 10 20
样例输出
34
40
虽然叶子结点的高度无法通过第二题的函数那样求出来,但是叶子结点的父亲的高度是肯定可以求出来的,叶子结点的父亲的高度通过上面那题的GetHeight求出来之后,再-1就可以得到叶子结点所处的高度。
用总高度减去叶子结点的高度就可以得到分支个数。也就是
71中的1,62中的2了。
#include <iostream>
using namespace std;
class treenode{
public:
char data;
treenode *left,*right;
int w;
treenode(){
left=NULL,right=NULL;
}
};
class tree{
treenode *root;
int road;
public:
tree(){road=0;}
void createtree(){
root=createbitree();
}
treenode* createbitree(){
treenode *t;
char a;
cin>>a;
if(a!='0'){
t=new treenode();
t->data=a;
t->left=createbitree();
t->right=createbitree();
}
else t=NULL;
return t;
}
void preorder1(treenode *t){
if(t!=NULL){
if(t->data>='A'&&t->data<='Z'){
int weight;
cin>>weight;
t->w=weight;
//road+=t->w*getHeight(t);
//cout<<"w:"<<weight<<" h:"<<getHeight(t)<<" road:"<<road<<endl;
}
preorder1(t->left);
preorder1(t->right);
}
else return;
}
void preorder2(treenode *t){
if(t!=NULL){
if(t->left!=NULL){
if(t->left->data>='A'&&t->left->data<='Z'){
road+=t->left->w*(getHeight(root)-(getHeight(t)-1));
}
}
if(t->right!=NULL){
if(t->right->data>='A'&&t->right->data<='Z'){
road+=t->right->w*(getHeight(root)-(getHeight(t)-1));
}
}
preorder2(t->left);
preorder2(t->right);
}
else return;
}
void preorder(){
preorder1(root);
//cout<<"1"<<endl;
preorder2(root);
//cout<<"2"<<endl;
}
int getHeight(treenode *root){
if(root==NULL)return 0;
int left=getHeight(root->left);
int right=getHeight(root->right);
if(left>right)return left+1;
else return right+1;
}
/*void printMaxRoad(){
treenode *t=root;
int sum=0;
do{
sum+=t->w;
}while(t->left!=NULL&&t->right!=NULL);
}*/
void printRoad(){
cout<<road<<endl;
}
};
int main()
{
int t;
cin>>t;
while(t--){
tree tree0;
tree0.createtree();
int num;
cin>>num;
tree0.preorder();
tree0.printRoad();
}
return 0;
}