数据结构荣誉课第三次上机实验
一、二叉树最长路径值:
题意:根据先根序列构建二叉树并求出最长最右的路径。
思路:
刚开始做此题时我采用递归调用求出树的高度,后在输出最长路径时每次都求一边左子树高度和右子树高度,再作比较,这样的代码,最后拿到了80分,时间复杂度是o(nlogn),第三个样例超时。于是我在树的节点中加了一个树高,这样在建树过程中就把每个节点的高度求出来,到时候比较时就是o(1)级了,建树时时间复杂度为o(logn)。
代码:
1)、节点:
typedef struct BTN{
int iterm;
BTN* left;
BTN* right;
int high;
BTN(int n):iterm(n),high(0){}
}btn,BtNode,*BinaryTree;
2)、判断输出:
for(int j=0;j<x;j++)
{
if(a.hi(p->left)==a.hi(p->right))
{
p=p->right;
printf(" %d",p->iterm);
}
else if(a.hi(p->left)>a.hi(p->right))
{
p=p->left;
printf(" %d",p->iterm);
}
else if(a.hi(p->left)<a.hi(p->right))
{
p=p->right;
printf(" %d",p->iterm);
}
}
3)、完整代码:
#include<iostream>
#include<algorithm>
#include<string.h>
#include<stack>
#include<malloc.h>
#include<queue>
using namespace std;
typedef char ElemType;
int b[100000],i=0;
typedef struct BTN{
int iterm;
BTN* left;
BTN* right;
int high;
BTN(int n):iterm(n),high(0){}
}btn,BtNode,*BinaryTree;
class tlist{
BTN*root;
public:
tlist():root(0){ };
inline BTN * CBT(int stop);//建树1已知数的先根序列加空结点位置
void pretravel(BTN*r);//先根遍历
inline int max(int a,int b){if(a>b) return a;else return b;}
inline int GetHeight( BTN* BT );
inline int hi(BTN *root);
};
int tlist:: hi(BTN*t)
{
if ( !t ) return -1;
return t->high;
}
BTN * tlist ::CBT(int stop)//树的操作类//涉及树的构建,树的遍历,指定节点查找
{
BTN *t1,*t,*t2;
int iterm;
scanf("%d",&iterm);
if(iterm==stop) {t=NULL;return t;}
else{
if(!(t=new BTN(iterm) )) return NULL;
t1=CBT(stop);
t->left=t1;
t2=CBT(stop);
t->right=t2;
t->high = max( hi(t->left), hi(t->right) ) + 1;
return root=t;
}
}
int tlist:: GetHeight(BTN* BT )
{
if(BT==NULL)
{
return -1;
}
else{
return 1+max(GetHeight(BT->left),GetHeight(BT->right));
}
}
int main()
{
tlist a;
int n;
scanf("%d",&n);
BTN*p;
p=a.CBT(-1);
int x;
x=a.GetHeight(p);
printf("%d\n%d",p->high,p->iterm);
i++;
for(int j=0;j<x;j++)
{
if(a.hi(p->left)==a.hi(p->right))
{
p=p->right;
printf(" %d",p->iterm);
}
else if(a.hi(p->left)>a.hi(p->right))
{
p=p->left;
printf(" %d",p->iterm);
}
else if(a.hi(p->left)<a.hi(p->right))
{
p=p->right;
printf(" %d",p->iterm);
}
}
return 0;
}
二、森林的层次遍历
题意:已知森林的先序序列及其对应的节点数求其层次遍历序列
思路:
做此题前一定要仔细审题,才开始我审成树的先序,然后一直段错误.................。此题有两种做法,一种是根据先序及节点数采用儿子兄弟链表法将森林转换为二叉树(栈),然后借助队列层次遍历。
另一种是我学习了其他同学代码后,才想到利用先根序列递归建树,这样简洁快速,并同时运用vector来保存每个节点儿子,这个是做题时高效的,首推此法。
代码
1)、非递归建树代码:(不推荐太麻烦但仅供提供思路,利用栈建树,队列)
#include<iostream>
#include<fstream>
#include<sstream>
#include<stack>
#include<queue>
using namespace std;
struct treenode{
char iterm;
int num;
treenode* leftc;
treenode* rightb;
treenode(char n):iterm (n),num(0),leftc(0),rightb(0){}
}*a[100050];
typedef treenode* ptn;
typedef treenode CBT;
class tlist{
public:
ptn root;
tlist() :root(0){}
};
stack <treenode*> q0;
int i=0;
CBT* levelorder(CBT*t);
int main()
{ char ch;
int n,s;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>ch;
a[i]=new treenode(ch);
}
for( i=0;i<n;i++)
{
cin>>s;
a[i]->num=s;
}
if(n==1) {cout<<a[0]->iterm;return 0;}
else{
int k=i-1;
for(;k>0;)
{
while(a[k]->num==0)
{
q0.push(a[k]);
k--;
}
ptn p0;
p0=q0.top();
q0.pop();
a[k]->leftc=p0;
a[k]->num--;
ptn p1=NULL;
while(a[k]->num!=0)
{
p1=q0.top();
q0.pop();
p0->rightb=p1;
p0=p1;
a[k]->num--;
}
q0.push(a[k]);
k--;
}
ptn root=new treenode(0);
root->leftc=a[0];
ptn qr;
int l=1;
if(q0.size()>1)
{ while(!q0.empty())
{if(l==1) { qr=q0.top();q0.pop();l++;}
else { ptn tmp;
tmp=q0.top();
q0.pop();
qr->rightb=tmp;
qr=tmp;
}
}
levelorder(root);
return 0;
}
else { levelorder(a[0]);}
return 0;
}
}
CBT* levelorder(CBT*t)
{
int sum=0;
typedef CBT* BinTree;
queue<BinTree> q;
if(t!=NULL)
{
BinTree p;
q.push(t);
while(!q.empty())
{ p=q.front();
q.pop();
printf("%c ",p->iterm);
p=p->leftc;
sum++;
cout<<sum<<" ";
if(sum==(i)){ break;}
while(p!=NULL)
{
q.push(p);
p=p->rightb;
}
}
printf("%c",q.front()->iterm);
}
cout<<endl;
return t;
}
2)、递归建树:
#include<stack>
#include<queue>
#include<vector>
#include<iostream>
#define maxa 100010
using namespace std;
char iterm [maxa];//若开在main函数内会堆栈溢出。
vector <int> a[maxa];//开向量存储儿子节点
int num[maxa];//将num,iterm开成全局变量不会使堆栈溢出
queue <int> q;
int pi=0;
void CBT(int x);//递归建树
int main()
{
int n=0,i=0;
scanf("%d",&n);
for( i=1;i<=n;i++)
cin>>iterm[i];
pi++;
for( i=1;i<=n;i++)
{
cin>>num[i];
}
while(pi<=n)
{
a[0].push_back(pi);//由于队列栈都是在双端队列基础上封装的,此将元素写入队尾
CBT(pi);
pi++;
}
q.push(0);
while(!q.empty())
{
int temp=q.front();
q.pop();
for(vector<int>::iterator pointer=a[temp].begin();pointer!=a[temp].end();pointer++)//向量遍历器
{
q.push(*pointer);
}
if(temp){
cout<<iterm[temp];
if(!q.empty())
printf(" ");
else printf("\n");
}
}
}
void CBT(int x)//递归建树
{
int i;
for( i=0;i<num[x];i++)
{
a[x].push_back(++pi);
CBT(pi);
}
}
三、纸带切割
题意:求出可达成切割目标的最小切割代价,以及每次切割时的代价;
思路:通过观察,你可已发现输出序列,为反哈夫曼树,于是我们可以利用哈夫曼编码方式解决。考试时,在这里我找了不必要的麻烦,其实没必要建树。我们只是模仿该过程即可于是,经过思考我采用堆方式优化排序求和(也可采用优先队列,不过要自己写优先级)的方式,即每求一次和时将此和放入队中下沉,最后再取堆顶元素。
代码:
1)、建堆以及维护堆:
void swap(long long int *x,long long int*y)
{
long long int t;
t=*x;
*x=*y;
*y=t;
}
void down1(long long int x ) // h[x]下沉
{
long long int i = x, y ;
while ( 2*i <= hlen && h[ 2*i ] <h[ i ] ||2*i+1<=hlen && h[ 2*i + 1 ]<h[ i ] ) {
y = 2 * i;
if ( 2*i+1<=hlen && h[ 2*i+1 ]<h[ 2*i ] ) y++;
swap(&h[i], &h[y]);
i = y;
}
}
void build1() {
hlen = n;
for(int i=1 ; i<=n ; i++) h[ i ]=a[ i ];
for(int i=n/2 ; i>=1 ; i--) down1( i );
}
void insert( int x ) // 插入x
{
hlen++;
h[hlen] = x;
down1 (hlen);
}
2)、完整代码:
#include<stdio.h>
long long int n=0,hlen=0,a[1000000],h[1000000];
void build1();
void down1(long long int x );
void swap(long long int *x,long long int*y);
void up(int x) ;
void insert( int x );
int main()
{ long long int a0[100000],a1,x=0,y=0,s=0;
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
build1();
long long int s1=1,k=0;
for(int i=1 ; i<n ; i++)
{x=h[1];
swap(&h[1], &h[hlen]);
hlen--;
down1(1);
y=h[1];
a0[k]=x+y;
s+=a0[k];
insert( a0[k] );
k++;
swap(&h[1], &h[hlen]);
hlen--;
down1(1);
}
printf("%lld\n",s);
for(int i=k-1;i>=0;i--)
{
printf("%lld",a0[i]);
if(i>0) printf(" ");
}
// printf("%d %d\n%d %d",a1,s2,a0,s1);
return 0;
}
void swap(long long int *x,long long int*y)
{
long long int t;
t=*x;
*x=*y;
*y=t;
}
void down1(long long int x ) // h[x]下沉
{
long long int i = x, y ;
while ( 2*i <= hlen && h[ 2*i ] <h[ i ] ||2*i+1<=hlen && h[ 2*i + 1 ]<h[ i ] ) {
y = 2 * i;
if ( 2*i+1<=hlen && h[ 2*i+1 ]<h[ 2*i ] ) y++;
swap(&h[i], &h[y]);
i = y;
}
}
void build1() {
hlen = n;
for(int i=1 ; i<=n ; i++) h[ i ]=a[ i ];
for(int i=n/2 ; i>=1 ; i--) down1( i );
}
void insert( int x ) // 插入x
{
hlen++;
h[hlen] = x;
down1 (hlen);
}
/*
5
5 6 7 2 4
*/
四、序列乘积:
题意:求两个等长递增序列的乘积中从小到大的前n个。
思路:
当时做的时候,首先用暴力法尝试,即乘完后快排,只拿了40分,后又优化:a,b数组存储两个递增序列, 缩小乘积数组c的长度使其长度为a的两倍,初始化时将a[1]与b数组
全部元素相乘后输入c中然后,每次乘n次,将结果赋予c[n+1]~c[n+2],归并排序一次(也可快排,或桶排序),均拿了60分,最后超时。于是课后参考其他同学,采用优先队列的方法写了一遍,才通过。我们利用单调序列的性质,跟刚才一样的初始化,后每次去最小,再从其右边取即可。
代码:
1)、参考节点:
struct node
{
int xn,yn;
lli multi;
bool friend operator<(const node&s1,const node&s2 )//定义优先级
{
return s1.multi>s2.multi;
}
node(int x,int y,lli v):xn(x),yn(y),multi(v){}
};
2)、关键代码,每次去队头后,入队对头右边元素:
for(int i=1;i<=n;i++)
{
node temp=s.top();//队头为最小值输出
printf("%lld",temp.multi);
s.pop();
if(temp.yn<n) s.push(node(temp.xn,temp.yn+1,a[temp.xn]*b[temp.yn+1]));//取完后直接让上一最小值右边元素入队;
if(i<n) printf(" ");
}
3)、完整代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long lli;
#define maxa 100005
int n;
lli a[maxa];//将a,b开成全局变量不会使堆栈溢出,若开在main函数内会堆栈溢出。
lli b[maxa];
struct node
{
int xn,yn;
lli multi;
bool friend operator<(const node&s1,const node&s2 )//定义优先级
{
return s1.multi>s2.multi;
}
node(int x,int y,lli v):xn(x),yn(y),multi(v){}
};
priority_queue<node>s;//声明优先队列
int main()
{
scanf("%d",&n);
lli temp;
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)
{
scanf("%lld",&b[i]);
if(i==1)
{
for(int j=1;j<=n;j++)
s.push(node(j,1,a[j]*b[1]));//初始化
}
}
for(int i=1;i<=n;i++)
{
node temp=s.top();//队头为最小值输出
printf("%lld",temp.multi);
s.pop();
if(temp.yn<n) s.push(node(temp.xn,temp.yn+1,a[temp.xn]*b[temp.yn+1]));//取完后直接让上一最小值右边元素入队;
if(i<n) printf(" ");
}
return 0;
}
/*
5
1 3 5 7 9
2 4 6 8 10
*/