P18顺序表
- ~ 5.在顺序表中删除值s-t之间(包含s,t)且s<t的所有元素,不合理的范围返回错误信息
从前向后扫描顺序表L
用k记录下元素值在s-t之间元素的个数(初始k=0)
对于当前扫描的元素,若其值不再s-t之间,则前移k个位置,否则k++
bool dele_st(sqlist &l,int s,int t){
if(s>=t||l.length==0) return false;
int k=0;
for(int i=0;i<l.length;i++){
if(l.data[i]>=s||l.data[i]<=t) k++;
else l.data[i-k]=l.data[i]; //i-k,只有不再范围内的值才做赋值,不存在i<k的情况
}
l.length-=k;
return ture;
}
- 【2】6.在有序表中删除所有重复值的元素
有序顺序表值相同的元素一定在连续的位置上,
初始时将第一个元素视为非重复的有序表,
之后依次判断后面的元素是否与前面非重复有序表的最后一个元素相同
若相同则继续向后判断
若不同则插入到前面的非重复有序表的最后,直至判断到表尾为止
void dele_copy(sqlist &l){
int i=0,j; //j是非重复有序表的最后一个元素
for(j=1;j<l.length;j++){
if(l.data[j]!=l.data[i]) //每次不等则前移
l.data[++i]=l.data[j]; //++i,用例子分析可以知道
}
l.length=i+1; //i+1,i是数组下标
}
- 【1】8.已知在一维数组A[m+n]中依次存放两个线性表。试编写函数将两个顺序表位置互换。
首先将数组的全部元素原地逆置,再对前n个元素和后m个元素分别使用逆置算法,从而实现顺序表位置互换。
整体求逆:(AB)逆=B逆A逆 各部分分别求逆:(B逆)逆(A逆)逆=BA
typedef int datatype; //下标都是int,只有数组中的内容可能是数字也可能是字符所以另写类型为了更好修改
void reverse(datatype A[],int left,int right,int arraysize){
if(left>=right||right>=arraysize)每次需要表长来判断是否违规
return;//return;表示什么都不返回,只结束此函数
//如果要逆转的数组元素超出总数组元素个数,则数组参数有误,什么都不返回
int mid=(left+right)/2
for(int i=0;i<mid-left;i++){ //注意第二段遍历范围
datatype temp=A[left+i];
A[left+i]=A[right-i];
A[right-i]=temp;
}
}
void exchange(datatype A[],int m,int n,int arraysize){//要给数据表长度,用来判断逆转的范围是否正确,这个长度是总的表长
reverse(A,0,m+n-1,arraysize); //分清什么时候用结构类型,什么时候不用
reverse(A,0,n-1,arraysize); //先总体求逆,再分别求逆
reverse(A,n,m+n-1,arraysize);
}
- 【1】9.线性表(a1..an)中元素递增有序且顺序存储,请查找值为x的元素,找到后与其后继交换位置,若找不到则将其插入表中并使表中元素仍递增有序
折半查找最快
void SearchExchangeInsert(ElemType a[],int n,ElemType x){
int low=0,high=n-1,mid;
while(low<=high){
mid=(low+high)/2;
if(a[mid]==x) break;
else if(a[mid]<x) low=mid+1;
else high=mid-1;
}
if(mid!=n-1&&a[mid]==x){ //查找成功,则与后继交换
int temp=a[mid+1];
a[mid+1]=a[mid];
a[mid]=temp;
}
if(low>high){ //查找,则插入元素
for(int i=n-1;i>high;i--) a[i+1]=a[i];
a[i+1]=x;
}
}
P38链表
- ~ 3.把带头结点的单链表从尾到头反向输出
方法1.先逆置再输出
void reverse(Linklist &l){
if(l||l->next) return;
Linklist *p=l->next,*temp;
l->next=NULL;
while(p->next){
*temp=p->next;
p->next=l->next;
l->next=p;
p=temp;
}
while(l->next){
printf("%d",l->next->data);
l=l->next;
}printf("%d",l->data);
}
方法2.递归(栈的思想)
每当访问一个节点时,先递归输出该节点自身,这样链表就反向输出了。
void R_print(Linklist l){
if(l->next) R_printf(l->next);
printf("%d",l->data);
}
- 【1】对带头结点的单链表排序(递增)
类似插入排序算法的思想,先构成只含有一个数据节点的有序单链表,然后依次扫描单链表中剩下的结点*p(直至p==NULL),在有序表中通过比较查找插入*p的前驱节点:*pre,然后将*p插入*pre之后
void sort_list(Linklist &l){
Linklist *pre,*p=l->next;
Linklist *r=p->next;//构成含一个节点的单链表,有序
p->next=NULL;
p=r;
while(p){
r=p->next;//存好位置
pre=l;//每次从头开始确定位置 1245中插入3
while(pre->next!=NULL&&pre->next->data<p->data)
pre=pre->next;
p->next=pre->next;
pre->next=p;
p=r;//然后p指向下一个待排元素
}
}
- 【1】9.递增输出一个单链表的所有节点的数据元素并释放节点存储空间
对链表进行遍历,在每次遍历中找出整个链表的最小值元素,输出并释放结点所占空间:再查找次小值元素,输出并释放空间。如此直至链表空,最后释放头结点。
void sort_list(Linklist l){
Linklist *min,*minpre,*p;
while(l->next){
min=l->next;
minpre=l;
p=l;
while(p->next){
if(p->next->data<min->data){
min=p->next;
minpre=min;
}
p=p->next;
}
printf("%d",min->data);
Linklist *temp;
temp=min;
minpre->next=min->next;
free(temp);
}
free(l);
}
- 【差口气,注意一下即可】10.分解带头结点的单链表,A表含有序号为奇数的元素,B表含有序号为偶数的元素
设置一个访问序号变量初值为0,每访问一个节点就序号+1,然后根据序号的奇偶性将节点插入到AB表中,重复操作直到表尾。
void apartlist(Linklist &A,Linklist &B){//A为原始链表,B中保存序号为偶数的元素
int i=0;
Linklist *p=A->next;
A->next=NULL;
B->next=NULL;
Linklist *ra=A,rb*=B;
while(p){
i++;
if(i%2==1){
p->next=ra->next;
ra=p;
}else{
p->next=rb->next;
rb=p;
}
p=p->next;
}
}
- ~ 13.将两个递增排列的单链表,归并为一个递减排列的单链表(要求利用原来两个单链表的结点存放归并后的单链表)
两个链表已经递增次序排列,合并时,均从第一个节点进行比较,较小的头插法链入表中,同时后移工作指针。若有剩余则依次头插法链入新链表中
void merge(Linklist &A,Linklist &B){
Lnode *pa=A->next,*pb=B->next,*temp;
A->next=NULL;
B->next=NULL;
while(pa!=NULL&&pb!=NULL){
if(pa->data<=pb->data) {
temp=pa->next; pa->next=A->next; A->next=pa;pa=temp;//头插
} else{
temp=pb->next; pb->next=B->next; B->next=pb; pb=temp;
}
}
while(pa!=NULL){
temp=pa->next;
pa->next=A->next;
A->next=pa;
pa=temp;
}
while(pb!=NULL){
temp=pb->next;
pb->next=B->next;
B->next=pb;
pb=temp;
}
free(B) ;
}
- 【1】92、归并排序
专用于合并两个不等长有序序列
先将A中所有元素复制到开辟的新数组B中,分别从两个不等长有序序列的第一个元素开始,两两比较将较小的复制回到A中,若有剩余则直接复制
int *B=(int *)malloc((n+1)*sizeof(int)); //为数组分配空间,全局变量直接用
void Merge(int A[],int low,int mid,int high){
for(int k=low;k<=high;k++){
B[k]=A[k];//将A中所有元素复制到B中
}
for(int i=low,j=mid+1,k=i;i<=mid&&j<=high;k++){
if(B[i]<=B[j]) A[k]=B[i++];//比较B的左右两段中的元素,将较小的复制到A
else A[k]=B[j++];
}
while(i<=mid) A[k++]=B[i++];
while(j<=high) A[k++]=B[j++];
}
- 【1】14.两个带头结点递增有序的单链表,把两个单链表中的公共元素产生新链表,要求不破坏原来的链表
表A、B都有序,可从第一个元素起依次比较A、B两表的元素,
若元素值不等,则值小的指针往后移
若元素值相等,则创建一个值等于两节点的元素值的新节点(因为不能破坏原来的链表),使用尾插法插入到新链表中,并将两个原表指针后移一位,直到其中一个链表遍历到表尾
void Get_common(Linklist A,Linklist B){
Lnode *p=A->next,*q=B->next,*r,*s;
Linklist C=(Linklist)malloc(sizeof(Lnode));
r=C;
while(p!=NULL&&q!=NULL){
if(p->data<q->data) p=p->next;
else if(p->data>q->data) q=q->next;
else{
s=(Lnode*)malloc(sizeof(Lnode));
s->data=p->data; //尾插
r->next=s;
r=s;
p=p->next;
q=q->next;
}
}
r->next=NULL;
}
- 【c】15.求两个递增排列的单链表的交集
采用归并的思想,设置两个工作指针pa pb对两个链表进行归并扫描,只有同时出现在两集合中的元素才链接到结果表中且仅保留一个,其他节点全部释放。当一个链表遍历完毕后,释放另一个表中的剩下所有节点
Linklist Union(Linklist &A,Linklist &B){
Lnode *pa=A->next,*pb=B->next,*temp;
pc=A;
while(pa!=NULL&&pb!=NULL){
if(pa->data<pb->data){
temp=pa;
pa=pa->next;
free(temp);
}
else if(pa->data>pb->data){
temp=pb;
pb=pb->next;
free(temp);
}
else{
pc->next=pa;
pc=pa;
pa=pa->next;
temp=pb;
pb=pb->next;
free(temp);
}
}
pc->next=NULL;
free(B);
return A;
}
- 【1】16.判断两个单链表中的数值,B中元素是否是A中元素的连续子序列
因为两个整数序列已经存入两个链表中,操作从两个链表的第一个结点开始,若对应数据相等,则后移指针;若数据不等,则A链表从上次开始比较结点的后继开始,B链表仍从第一个结点开始比较,直到B链表到尾表示匹配成功。A链表到尾而B链表未到尾表示失败。操作中应记住A链表每次的开始结点,以便下次匹配时好从其后继开始
int Pattern(Linklist A,Linklist B){
Lnode *pa=A,*pb=B,*pre=A;
while(pa!=NULL&&pb!=NULL){
if(pa->data==pb->data){ //结点值相同,继续检查
pa=pa->next;
pb=pb->next;
}
else{ //结点值不同,让pa回到pre->next,让pb回到头上重新比较
pre=pre->next;
pa=pre;
pb=B;
}
if(pb==NULL) return 1;
else return 0;
}
}
- ~ 19.结点均为正整数的带头结点单链表,反复找出最小节点并输出,然后删除,直到链表为空,在删除表头结点
表不空时用一个循环遍历单链表,每循环一次查找一个最小值结点(minp保存最小值结点,minpre指向最小值节点前一个节点),输出最小值节点,然后删除,最后释放头结点。
void Delete_min(Linklist &L){
Lnode *minp,*minpre,*p,*pre;
while(L->next!=L){
p=L->next;
pre=L;
minp=p;
minpre=pre;
while(p!=L){
if(p->data<minp->data){
minp=p;
minpre=pre;
}
pre=p;
p=p->next;
}
printf("%d",minp->data);
minpre->next=minpre->next->next;
free(minp);
}
free(L);
}
- 【1】20.头指针为L的带表头结点非循环双链表,结点除了前驱pre后继next数据data还包括一个访问频度freq。在链表启用前,全部初始化为0.。每当在链表中进行一次Locate(L,x)运算时,令元素值为x的f、频度域值+1,并使此链表以访问频度排序,且相同频度结点时最近访问的结点排在前面,以便使频繁访问的结点总是靠近表头。请编写符合要求的Locate函数,以指针型返回找到结点的地址
主要考察双链表的查找、删除和插入。
首先在双向链表中查找数据值为x的结点,查到后,将节点从链表上摘下,然后在顺在结点的前驱查找该节点的插入位置(频度递减,且排在同频度第一个,即向前找到第一个比它的频度大的结点,插入位置为该节点之后)并插入到该位置
typedef struct LinkNode{
int data;
struct LinkNode *pre;
struct LinkNode *next;
int freq;
}Linklist;
Linklist* Locate(Linklist L,int x){
Linklist *p=L,*temp,*q=L;
while(p&&p->data!=x) p=p->next;
if(!p) return NULL;
else{
q=p->prior;
p->next->prior=p->prior;
p->prior->next=p->next;
p->freq++;
while(q->next&&q->next->freq>p->freq){
q=q->next;
}
p->next=q->next;
p->next->prior=q;
q->next=p;
p->prior=q;
}
}
return L;
}
DLinklist Locate(DLinklist &L,int x){
Dnode *p=L->next,*q;
while(p&&p->data!=x) p=p->next;
if(!p){
printf("不存在值为x的结点\n");
exit(0);
}
else{
p->freq++;
q=p->prior;
p->next->prior=p->prior;
p->prior->next=p->next;
while(q!=L&&q->freq<=p->freq) q=q->prior;
p->next=q->next;
q->next->prior=p;
p->prior=q;
q->next=p;
}
return p;
}
栈p66 3-5
- ~ 3.判定栈操作序列是否合法
依次逐一扫描入栈出栈序列,每扫描至任一位置均需检查出栈次数是否小于入栈次数,若大于则为非法序列。扫描结束后,再判断入栈和出栈次序是否相等,若不相等则不合题意。
bool Judge(char A[]){
int i=0; //用来遍历
int j=k=0;
while(A[i]!='\0'){
switch(A[i]){
case 'I': j++; break;
case 'O': k++;
if(k>j) {
printf("序列非法\n");
exit(0);
}
}
i++;
}
if(j!=k){
printf("序列非法\n");
return false;
}
else{
printf("序列合法\n");
return true;
}
}
- 【1细节】5.两个顺序栈共享0-Maxsize-1的存储区,请设计s1 s2有关出入栈的操作
两个栈共享向量空间,将两个栈的栈底设在向量两端,初始时,s1栈顶指针为-1,s2栈顶指针为Maxsize,两个栈顶指针相邻时为栈满。两个栈顶相向,迎面增长,栈顶指针指向栈顶元素。
typedef struct Snode{
int stack[Maxsize];
int top[2];
}Stack;
Stack s;全局变量
int push(int i,int x){
if(s.top[0]+1==s.top[1]) return 0;
switch(i){
case 0:s.stack[++s.top[0]]=x;return 1;break;
case 1:s.stack[--s.top[1]]=x;return 1;
}
}
int pop(int i){
switch(i){
case 0:
if(s.top[0]==-1) return -1;
else return s.stack[s.top[0]--];
case 1:
if(s.top[1]==Maxsize) return -1;
else return s.stack[s.top[1]++];
}
}
#define Maxsize 100
#define elementype int
typedef struct {
elementype stack[Maxsize];
int top[2];
}stk;
stk s;
int push(int i,elementype x){ //i时栈号
//入栈成功返回1,否则返回0
if(i<0||i>1){
printf("输入栈号不对");
exit(0);
}
if(s.top[1]-s.top[0]==1){
printf("栈满");
return 0;
}
switch(i){
case 0: s.stack[++s.top[0]]=x;
return 1;break;
case 1: s.stack[--s.top[1]]=x;
return 1;
}
}
int pop(int i){
//出栈成功返回栈顶元素,否则返回-1
if(i<0||i>1){
printf("输入栈号不对");
exit(0);
}
switch(i){
case 0:
if(s.top[0]==-1){
printf("栈空");
return -1;
}
else return s.stack[s.top[0]--];
case 1:
if(s.top[1]==Maxsize) {
printf("栈空");
return -1;
}
else return s.stack[s.top[1]++];
}
}
队列p81 1-3
- 【1】1.用标志域tag0或1来区分循环队列空满,请编写入队和出队算法
初始状态:tag=0 front=rear=0
#define Maxsize 100
typedef struct{
int front,rear,tag;
int data[Maxsize];
}Q;
int enque(sqQue &Q,elementype x){
if(Q.front==Q.rear&&Q.tag==1) return 0;
Q.data[Q.rear]=x; //rear指向空位置
Q.rear=(Q.rear+1)%Maxsize;
Q.tag=1; //可能队满
return 1;
}
int deque(sqQue &Q,elementype x){
if(Q.rear==Q.front&&Q.tag==0) return 0;
x=Q.data[Q.front]; //front指向队头元素
Q.front=(Q.front+1)%Maxsize;
Q.tag=0; //可能队空
return 1;
}
- 【1】天勤6、假设带头结点的循环链表表示队列,设一个指针指向队尾结点,请写出相应的入队出队算法
出队要判空,出队之后要判断是否是最后一个节点
入队节点要分配空间再入队
int Dequeue(Linklist &rear,int &x){
if(rear->next= rear) return 0;
else{
Lnode *p=rear,*temp;
p=rear->next->next;
rear->next->next=p->next;
x=p->data;
if(p==rear) rear=rear->next;
free(p);
return 1;
}
}
void Enqueue(Linklist *&rear,int x){
Linklist *p=(Linklist *)malloc(sizeof(LinkNode));
p->data=x;
p->next=rear->next;
rear->next=p;
rear=p;
}
栈和队列的应用p90 1-4
- 【注意一下即可】1.括号配对
扫描每个字符,遇到左括号进栈,遇到右括号时检查栈顶是否为相应左括号,若是:退栈,否则匹配错误,最后若栈不空也为匹配错误。
bool BracketCheck(char str*){
Initstack(s);
int i=0;
while(str[i]!='\0'){
switch(str[i]){
case '(': push(s,'(');break;
case '{': push(s,'{');break;
case '[': push(s,'[');break;
case ')': pop(s,x);if(x!='(') return false;break;
case '}': pop(s,x);if(x!='{') return false;break;
case ']': pop(s,x);if(x!='[') return false;break;
default: break;
}
i++;
}
if(!isEmpty(s)){
printf("括号不匹配");
return false;
}
else{
printf("括号匹配");
return true;
}
}
二叉树的概念 p113 5
- 【1】已知一棵二叉树按顺序存储结构进行存储,设计算法求编号i、j的最近公共祖先结点
二叉树中任意两个结点必然存在最近的公共祖先结点,最差的情况是根节点,而且从最近的公共祖先结点到根节点全部祖先节点都是公共的。由二叉树顺序存储的性质可知,任意节点i的双亲结点编号为i/2。
求解i、j的最近公共祖先结点的算法步骤如下(设数组下标从1开始存储):
1、若i>j,则结点i的所在层次大于等于结点j的所在层次,结点i的双亲结点为结点i/2,若i/2=j,则找到最近公共祖先结点j,若不等,则令i=i/2,即以该节点i的双亲结点为起点,采用递归的方法继续查找。
2、同理i<=j
重复过程,直到找到他们最近的公共祖先结点为止
ElemType Comm_Ancester(Sqtree T,int i,int j){
if(T[i]!='#'&&T[j]!='#'){ 根节点存在
while(i!=j){
if(i>j) i=i/2;
else j=j/2;
}
return T[i];
}
}
二叉树的遍历和线索二叉树 p126 3-18
- 【1】3.编写后序遍历二叉树非递归算法
后序非递归遍历二叉树的顺序是先访问左子树,再访问右子树,最后访问根节点,当用堆栈来存储节点时必须分清返回根节点时是从左子树返回的还是从右子树返回的。所以使用辅助指针r,其指向最近访问过的结点。
void PostOrder(BiTree T){
InitStack(S);
p=T;
r=NULL;
while(p||!IsEmpty(S)){
if(p){
push(S,p);
p=p->lchild;//向左走到底,全部入栈
}
else{
GetTop(S,p);
if(p->rchild&&p->rchild!=r){ //如果有右子树并且没访问过
p=p->rchild; //对右子树全部做同样操作
push(S,p);
p=p->lchild;
}
else{ //没有右子树或右子树访问过了,此时可以输出栈中节点值,这是个根节点(没有左子树也没有右子树或根据后序遍历右子树已经访问过了,此时可以输出根节点了)
pop(S,p);
printf("%d",p->data);
r=p; //记录最近访问的结点
p=NULL; //结点访问完后,重置p指针
}
}
}
}
- ~4.试给出二叉树自下而上,从右到左的层次遍历算法
一般层次遍历时自上而下从左到右,这里遍历顺序恰好相反
利用原有的层次遍历算法,出队的同时将各节点指针入栈,在所有节点入栈后再从栈顶开始依次访问。
具体实现如下:1、根节点入队。2、一个元素出队,遍历这个元素。3、依次把这个元素的右孩子左孩子入队。4、队列不空则跳到2,否则结束。
void InvertLevel(BinTree bt){
Stack S;
Queue Q;
BiTree p;
InitStack(S); //初始化栈,栈中存放二叉树结点的指针
InitQueue(Q); //初始化队列,队列中存放二叉树结点的指针
EnQueue(Q,bt);
while(!IsEmpty(Q)){
DeQueue(Q,p); //出队入栈
Push(S,p); //入栈
if(p->lchild)
EnQueue(Q,p->lchild); //入队左右孩子
if(p->rchild)
EnQueue(Q,p->rchild);
} //队空时,全部进栈了
while(!IsEmpty(S)){
Pop(S,p); //出栈可得反向序列
printf("%d",p->data);
}
}
- ~基于层次遍历
void LevelOrder(BiTree T){ 层次遍历不是递归的所以不需要边界条件
Queue Q;
InitQueue(Q);
BiTree p;
EnQueue(Q,T);
while(!IsEmpty(Q)){
DeQueue(Q,p);
visit(p);
if(p->lchild!=NULL) EnQueue(Q,p->lchild);
if(p->rchild!=NULL) EnQueue(Q,p->rchild);
}
}
- 【2】5.假设二叉树采用二叉链表存储结构,设计一个非递归算法求二叉树的高度(层次遍历算法)
采用层次遍历的算法,设置变量level记录当前节点所在层数,设置变量last指向当前层最右节点,每次层次遍历出队时与last指针比较,若两者相等,则层数加一,并让last指向下一层的最右节点,直到遍历完成。level的值即为树的高度。
非递归
int Btdepth1(BinTree T){
if(T==NULL) return 0;
int front=-1,rear=-1;
int last=0,level=0;
BinTree Q[MaxSize]; 数组存储的是树的节点,类型要写对
Q[++rear]=T; //入队
BinTree p;
while(front<rear){ 非循环的顺序的判空条件
p=Q[++front]; //出队
if(p->lchild) Q[++rear]=p->lchild; //入队左右孩子
if(p->rchild) Q[++rear]=p->rchild;
if(front==last){
level++; //妙,rear每次指向层中最后一个节点
last=rear;
}
}
return level;
}
- 【1】递归
int Btdepth2(BinTree T){
if(T==NULL) return 0; //递归出口
int ldep=Btdepth(T->lchild);
int rdep=Btdepth(T->rchild);
if(ldep>rdep) return ldep+1; //树的高度=子树最大高度+1
else return rdep+1;
}
- 【1】6.根据前序序列和中序序列构造二叉树,假设结点数据值互不相同。
确定根节点在中序中的位置,将中序序列分为两个子序列,递归处理直到所处理的子序列只剩下一个元素
BTnode *CreateBt(char pre[],char in[],int l1,int r1,int l2,int r2){
BTnode *s=(BTnode *)malloc(sizeof(BTnode));
int i;
if(l1>r1) return NULL;
s->lchild=s->rchild=NULL;
for(i=l2;i<=r2;i++)
if(in[i]==pre[l1]) break;
s->data=in[i]; //确定根节点位置
s->lchild=CreateBt(pre,in,l1+1,l1+i-l2,l2,i-1);
s->rchild=CreateBt(pre,in,l1+i-l2+1,r1,i+1,r2);
return s;
}
- 【1】7.二叉树按二叉链表形式存储,写一个判别给定二叉树是否是完全二叉树的算法(层次遍历算法)
根据完全二叉树的定义,具有n个结点的完全二叉树和满二叉树中编号从1-n的结点一一对应。
层序遍历把所有节点入队,对左右孩子入队时,不论是否空都入队
一旦层序遍历的过程中发现空节点,则检查队列中是否有非空节点,
此时队列中的节点时未来要遍历的节点也是刚才遍历的节点孩子,
若此时出现了非空节点,说明不是个完全二叉树,有部分节点是空的
采用层次遍历算法,将所有结点加入队列(包括空节点)。遇到空节点时,看其后是否右非空节点。若有,则不是完全二叉树。
bool IsComplete(BiTree T){
InitQueue(Q);
if(T==NULL)
return 1;//空树为满二叉树
EnQueue(Q,T);
while(!IsEmpty(Q)){
DeQueue(Q,p);
if(p!=NULL){
EnQueue(Q,p->rchild);
EnQueue(Q,p->lchild);
}
else
while(!IsEmpty(Q)){
//遍历的时候空节点也入队了,此时表示出现结点为空结点而队列非空
//若空节点之后又出现非空节点,那么一定不是完全二叉树
DeQueue(Q,p);
if(p) return 0;
}
}
return 1;
}
- 【1】10.假设二叉树采用二叉链表存储结构存储,设计一个算法,求先序遍历序列中第k(1<=k<=二叉树结点个数)个结点的值
设置一个全局变量 i 记录已访问过的结点的序号,其初值是根节点在先序序列中的序号,即1.当二叉树b为空时返回特殊字符“#”,当i==k时表示找到了满足条件的结点,返回b->data;当i!=k时,递归的在左子树中查找,若找到则返回该值,否则继续递归的在右子树中查找,并返回其结果。 (二叉树的遍历算法可以引申出大量的算法题,必须熟练掌握(本题基于前序遍历算法:访问根节点,递归访问左右子树(添加改动:对根节点进行递归出口判断第k个值是否存在)))
采用先序非递归遍历算法,每次出栈时看是否是第k个节点,若找到第k个节点就返回输出其值
int i=1; //记录已经访问过的序号
ElemType PreNode(BiTree b,int k){
if(b==NULL) return '#';
if(i==k) return b->data;
i++;
ch=PreNode(b->lchild,k);
if(ch!='#') return ch;
ch=PreNode(b->rchild,k); return ch;
}
- 【1】11.已知二叉树以二叉链表存储,编写算法:对于树中每个元素值为x的结点,删去以它为根的子树,释放相应的空间
删除以元素值x为根的子树,只要能删除其左右子树,就可以释放值为x的根节点,因此宜采用后序遍历。
删除值为x的结点,意味着应将其父节点的左右孩子指针置空,用层次遍历易于找到某结点的父节点。与链表删除节点时相同,用父节点的指向来判断是否相等,相等则删除,避免了找前驱的工作。删除之后别忘了置空此孩子节点。本题要求删除树种每个元素值为x的结点的子树,因此要遍历完整二叉树。
void DeleteXTree(BiTree bt){
//后序遍历:删除树,先删左右子树,再删根节点
if(bt){
DeleteXTree(bt->lchild);
DeleteXTree(bt->rchild);
free(bt);
}
}
void Search(BiTree bt,Elemtype x){
Queue Q;
InitQueue(Q);
EnQueue(Q,bt);
if(bt==NULL) return;
if(bt->data==x){
DeleteXTree(bt);
return ;
}
while(!IsEmpty(Q)){
DeQueue(Q,p);
if(p->lchild){
if(p->lchild->data==x){
DeleteXTree(p->lchild);
p->lchild=NULL;
}
else EnQueue(Q,p->lchild);
}
if(p->rchild){
if(p->rchild->data==x){
DeleteXTree(p->rchild);
p->rchild=NULL;
}
else EnQueue(Q,p->rchild);
}
}
}
- 【1】12.在二叉树中查找值为x的结点,试编写算法打印值为x的结点的所有祖先,假设值为x的结点不多于一个
一般情况下树的题目,用递归比较好
判断左右子树里有没有x,从下向上输出所有祖先结点
bool Ancestors(Node *root,int x){
if(root==NULL) return false;
if(root->data==x) return true; //这两个出口为下一个递归判断条件服务
if(Ancestors(root->lchild,x)||Ancestors(root->rchild,x)){
printf("%d",root->data); //任一孩子有x就输出这个根
return true;
}
return false;
}
- 【1】13.设一棵二叉树的结点结构为(LLINK,INFO,RLINK),ROOT为指向该二叉树根节点的指针,p和q分别为指向该二叉树种任意两个结点的指针,试编写算法ANCESTOR(ROOT,p,q,r),找到p和q的最近公共祖先结点r。(递归)
递归:递归遍历左右子树,找pq,递归结束后可以知道pq在左子树还是右子树,
若pq都不在左子树,那么他们都在右子树,右子树先遍历到的结点就是公共祖先
若left不空,说明左子树中有节点,那么看看右子树,若right时空的,说明全在左子树,那么左子树中先遍历到的就是公共祖先
否则,left和right都不空,说明pq在root异侧,则root就是公共祖先
注意:结点本身也可以是自己的祖先
struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q){
if(root==NULL||root==q||root==p)
return root;
struct TreeNode *left = lowestCommonAncestor(root->left, p, q);
struct TreeNode *right = lowestCommonAncestor(root->right, p, q);
if(left==NULL) return right;
if(right==NULL) return left;
return root;
}
- 【1】16.设计一个算法将二叉树的叶节点按从左到右的顺序连成一个单链表,表头指针为head。
二叉树按二叉链表的方式存储,链接时用叶节点的右指针域来存放单链表指针
通常使用的树的遍历对叶节点的访问都是从左到右,所以都可以,这里采用中序递归遍历。设置前驱指针为pre,初始为空。第一个叶节点由指针head指向,遍历到叶节点时,就将它前驱的rchild指针指向它,最后一个叶节点的rchild为空。
重要的是要知道前驱在哪里,每次用一个全局变脸pre来保存当前的节点,
下次在找到叶子节点只需要将pre的右指针指向它
head,pre的类型是保存二叉树节点的链表节点
LinkList *head=NULL,*pre=NULL;
LinkList* PreOrder(BiTree t){
if(t){
if(t->lchild==NULL&&t->rchild==NULL){
if(pre==NULL){
pre=t;
head=t;
}
else{
pre->rchild=t;
pre=t;
}
}
PreOrder(t->lchild);
PreOrder(t->rchild);
pre->rchild=NULL;别忘了最后一个指针置空
}
return head;
}
- 【1】17.试设计判断两棵二叉树是否相似的算法。所谓二叉树T1T2相似,指的是都空或都只有一个根节点或左子树与左子树相似且右子树与右子树相似
本题采用递归思想,若都空,则相似;若一个空一个不空,则必然不相似;否则递归的比较他们的左右子树是否相似
两空,一空,都不空的三种情况
int Similar(BiTree t1,BiTreet2){
if(t1==NULL&&t2==NULL) return 1;
else if(t1==NULL||t2==NULL) return 0;
else{
left=Similar(t1->lchild,t2->lchild);
right=Similar(t1->rchild,t2->rchild);
return left&&right;
}
}
- ~(7)二叉链表存储二叉树,设计算法求二叉树b中值为x的结点的层号
相等就打印当前的层次,不等就++,每次递归出来要--,注意的是与后题区分:后题是二叉排序树
int L=1;
void high_x(BTnode *p,int x){
if(p){
if(p->data==x) printf("%d",L);
++L;
high_x(p->lchild,x);
high_x(p->rchild,x);
--L;
}
}
- 【1】7.求出指定节点在给定二叉排序树中的层次
实际上就是二分查找,每次比较一次就层数加一。设二叉树采用二叉链表存储结构。在二叉排序树中,查找一次就下降一层。因此,查找该节点所用的次数就是该节点在二叉排序树中的层次。采用二叉排序树非递归查找算法,用n保存查找层次,每查找一次,n就加1,直到找到相应的结点
int level(BiTree bt,BSTNode *p){
int n=0; //统计查找次数
BiTree t=bt;
if(bt!=NULL){
n++; //树非空则n最低为1
while(t->data!=p->data){
if(t->data<p->data) t=t->rchild;
else t=t->lchild;
n++;//比较一次层数+1
}
}
return n;
}
(11)设中序线索二叉树类型为TBTnode *InThTree
- 【2】(1)设计算法在一棵中序线索二叉树中寻找节点t的子树上中序下的最后一个节点
沿着t的右子树链一直走下去,直到遇到其右指针为右线索的节点为止
TBTnode *inLast(TBTnode *t){
TBTnode *p=t;//此处因为考虑到如果没有右孩子的话中序最后一个节点就是他自己
while(p&&!p->rtag) p=p->rchild;
return p;
}
- 【2】(2)找t的中序前驱
若t有左线索,则其左线索所指结点即为中序前驱;若无左线索,则其左子树中中序最后一个节点为它的中序前驱
TBTnode *inPrior(TBTnode *t){
TBTnode *p=t->lchild;//有线索是线索
if(p&&!t->ltag) p=inLast(p);//无左线索是则是当前节点的中序最后一个节点
return p;
}
- 【2】(3)找t的前序后继
分三种情况:1、节点有左孩子 2、节点只有右孩子 3、节点没有孩子,此时其前序后继是此节点的右线索走到无右线索节点这个节点就是前序后继
TBTnode *preNext(TBTnode *t){
TBTnode *p;
if(!t->ltag) p=t->lchild;
else if(!t->rtag) p=t->rchild;
else{
p=t;
while(p&&p->rtag) p=p->rchild;
if(p) p=p->rchild;
}
return p;
}
树、森林 p152 5-7
- 【1】5.编程求以孩子兄弟表示法存储的森林的叶子节点数
当森林以孩子兄弟表示法存储(只要结点没有左孩子,则必定是叶子)时,若结点没有孩子,则必定是叶子,总的叶子结点个数是孩子子树上的叶子树和兄弟子树上的叶节点个数之和。
没有左孩子,叶子数=1+兄弟子树叶子数(只有右孩子,分开之后,这个单独的节点也是一个叶子)
有左孩子,叶子数=孩子子树叶子数+兄弟子树叶子数
typedef struct node{
ElemType data;
struct node *fch,*nsib;
}*Tree;
int Leaves(Tree t){
if(t==NULL) return 0;无需判断孩子兄弟都空的情况
if(t->fch==NULL) return 1+Leaves(t->nsib);
else return Leaves(t->fch)+Leaves(t->nsib);
}
- 【1】6.以孩子兄弟链表为存储结构,请设计递归算法求树的深度
由孩子兄弟链表表示的树,求高度的算法思想如下:
采用递归算法,
若树为空,高度为0;
否则高度为第一子女树高+1和兄弟子树高度的大者。
左孩子右兄弟,只有孩子节点增加高度(只有第一子女+1是树的高度,其余的子女全是作为兄弟,所以不增加1)
空节点为0
非空节点则递归判断子树高度和兄弟的高度,注意只有孩子节点使树的高度增加
int Depth(BiTree t){
int hc,hb;
if(t==NULL) return 0;
else{
hc=Depth(bt->child);
hb=Depth(bt->brother);
return hc+1>hb? hc+1:hb;
}
}
树和二叉树的应用 p168 6-10
- 【3】6.判断给定二叉树是否是二叉排序树
对于二叉排序树来说,其中序遍历序列为一个递增有序序列。因此,对给定的二叉树进行中序遍历,若始终能保持前一个值比后一个值小,则为二叉排序树。
关键是把前一个值找好,每次都要比较的
int predt=-32767;
int JudgeBst(BiTree t){
int t1,t2;
if(t==NULL) return 1;
else{
t1=JudgeBst(t->lchild);
if(t1==0||predt>=t->data) return 0;
predt=t->data;
t2=JudgeBst(t->rchild);
return t2;
}
- 【3】8.利用二叉树遍历的思想编写一个判断二叉树是否是平衡二叉树的算法
void Judge_AVL(BiTree bt,int &balance,int &h){
balance:是否平衡的标志,0不平,1平衡的
int bl=0,br=0,hl=0,hr=0; //左右子树平衡标记和高度,初始都是0,
if(bt==NULL){
h=0;
balance=1;
}
else if(bt->lchild==NULL&&bt->rchild==NULL){
h=1;
balance=1;
}
else{
Judge_AVL(bt->lchild,bl,hl);//blbr在递归中不断被改变,并被带回来
Judge_AVL(bt->rchild,br,hr);
h=(hl>hr?hl:hr)+1;
if(abs(hl-hr)<2) balance=bl&&br;
else balance=0; 每次就是靠h和balance来判断平衡的,也是函数参数一直使用的
}
}
- 【1】10.设计算法,从大到小输出二叉排序树中所有值不小于k的关键字
本来想先入栈再输出,但是中序遍历左根右是递增,中序右根左就是递减了,妙
void OutPut(BSRNode *bt,KeyType k){
if(bt==NULL) return;
if(bt->rchild) OutPut(bt->rchild,k);
if(bt->data>=k) printf("%d",bt->data);
if(bt->lchild) OutPut(bt->lchild,k);
}
顺序查找和折半查找 p247 6-7
- 【1】6.折半查找的递归算法
ElemType array[];
int BinSearchRec(ElemType key,int low,int high){
if(low>high) return -1;
int mid=(low+high)/2;
if(key>array[mid])
Search(key,mid+1,high);
else if(key<array[mid])
Search(key,low,mid-1);
else return mid;
}
- 【1】7、折半查找的非递归
折半:每次与中间节点比较,不断缩小比较范围,直到下标交叉
快排:提前存好一个节点,再依次每个节点与与枢轴比较,放入空位直到下标重合
int BinarySort(int L,int low,int high,int key){
while(low<=high){ //快排low<high
int mid=(low+high)/2;
if(key==L[mid]) return mid;
else if(key<L[mid]) high=mid-1;
else low=mid+1;
}
return -1;
}
- 【1】快速排序
并非双指针哦,快排的划分,提前得到了一个空位置,每次找到了填进去即可又得到一个空位置,所以每次都是单循环的
void QuickSort(int L[],int low,int high
if(low<high){
int pivot=Partition(L,low,high);
QuickSort(L,low,pivot-1);
QuickSort(L,pivot+1,high);
}
}
int Partition(int L[],int low,int high){
int pivot=L[low];
while(low<high){
while(low<high&&A[high]>pivot) high--;
L[low]=L[high];
while(low<high&&A[low]<pivot) low++;
L[high]=L[low];
}
L[low]=pivot;
return low;
}
交换排序 p303 2-7
- 【2】冒泡排序递减
冒泡排序的结束标志是一次排序中没有关键字发生交换
void Bubble(int A[],int n){
for(int i=0;i<n-1;i++){
flag=false; //标志本趟冒泡是否交换
for(int j=n-1;j>i;j--){ 冒泡过程
if(A[j]<A[j-1]){
swap(A[j],A[j-1]);
flag=true;
}
}
if(flag==false) return ;
}
}
- 【3】2.双向冒泡算法,在正反两个方向交替进行扫描,即第一趟把关键字最大的元素放在序列最后,第二趟把关键字最小的元素放在序列最前,如此反复进行。
奇数趟时,从前向后比较相邻元素的关键字,遇到逆序即交换,直到把序列中关键字最大的元素移动到序列尾部。
偶数趟时,从后往前比较相邻元素的关键字,遇到逆序即交换,直到把序列中关键字最小的元素移动到序列前端。
void bubblesort(ElemType A[],int n){
int low=0,high=n-1;//
bool flag=true; //当排序过程中没有发生交换时说明排序完成
while(low<high&&flag){
flag=false;
for(i=low;i<high;i++) //严格大于是因为交换的时候多影响了一个值
if(a[i]>a[i+1]){
swap(a[i],a[i+1]);
flag=true;
}
high--; //更新上界,注意,在for循环的外面
for(i=high;i>low;i--)
if(a[i]<a[i-1]){
swap(a[i],a[i-1]);
flag=true;
}
low++;
}
}
- 【3】3.已知线性表按顺序存储,且每个元素都是不相同的整数型元素,设计把所有的奇数移动到偶数前面的算法。
可采用基于快速排序的划分思想来设计算法,只需要遍历一次即可,时间On空间O1
假设表为L[1..n] 找到第一个偶数和第一个奇数,使他们交换,则不会出现交换后错过了某个数的情况
从前向后找到一个偶数元素L(i)
从后向前找到一个奇数元素L(j)
将二者交换,重复过程直到i>j
void move(ElemType A[],int len){
int i=0,j=len-1;
while(i<j){ //快排是重合,查找是交叉
while(i<j&&A[i]%2!=0) i++;
while(i<j&&A[j]%2!=1) j--;
if(i<j){
swap(A[i],A[j]);
i++;j--;
}
}
}
- 【2】5.编写算法,在数组L[1..n]中找出第k小的元素(即从小到大排序后处于第k个位置的元素)
快排划分操作
从数组中选择枢轴pivot,进行快排的划分操作,表被划分为L[1..m-1]L[m+1..n],其中L(m)=pivot
落在哪个区间上就对这个区间递归查找这个元素
时间复杂On,空间复杂取决于划分方法
int Searchkth(int A[],int low,int high,int k){
int pivot=A[low];
int low_temp=low;//low在划分中被改变,但是在递归中还要用,所以要保存起来
int high_temp=high;
while(low<high){
while(low<high&&A[high]>=pivot) --high;
A[low]=A[high];
while(low<high&&A[low]<=pivot) ++low;
A[high]=A[low];
}
a[low]=pivot;别忘了
if(low==k) return low;
else if(low>k) return searchkth(a,low_temp,low-1,k);
else return searchkth(a,low+1,high_temp,k-low);
}
- 【2】7.荷兰国旗问题:设有一个仅由红白蓝三种颜色的条块组成的条块序列,编写时间复杂度On的算法,使这些条块按红白蓝顺序排好。
顺序扫描线性表,将红色交换到最前面,蓝色交换到最后
设立三个指针,j为工作指针表示当前扫描的元素,i以前全部为红色,k以后全部为蓝色。根据j所指示元素的颜色,决定将其交换到序列的前部或尾部(enum是计算机编程语言中的一种数据类型。枚举类型:在实际问题中,有些变量的取值被限定在一个有限的范围内。)
typedef enum{
RED;WHITE;BLUE;
}color;
void Flag(color a[],int n){
int i=0,j=0,k=n-1;
while(j<=k){
switch(a[j]){
case RED: swap(a[i],a[j]); i++; j++; break;//是红色换到最前面去
case WHITE: j++; break;
case BLUE: swap(a[j],a[k]); k--;//是蓝色换到最后面去
//为了防止蓝红白蓝的情况所以不能j++,蓝红白蓝、白红蓝蓝、红白蓝蓝
}
}
}
选择排序 p314 4-5
- 【2】4.编写算法,在基于单链表表示的待排序关键字序列上进行简单选择排序递增(无头结点)
每趟在原始链表中摘下关键字最大的结点,插入到结果链表的最前端(比较于顺序表的逻辑:顺序表是找到最值节点然后与头上的节点比较然后交换。链表是真的选择之后要链接过来,也就是没有交换,是直接过来了)
遍历链表,用p和pre找最大节点,用max和maxpre确定最大节点,h用来遍历链表,l是排序后的链表表头(无头结点)(需要前驱的原因是,需要摘下节点,需要pre的原因是,遍历的时候更方便,不需要单独考虑下一个节点的情况,也可以直接赋给maxpre)
若发现最大节点就在头上,则继续对剩下元素选择排序
若不在头上,则取下最大节点连接到无头结点的l表中,虽然是头插但是,因为不是使用头结点插入的所以也相当于在头上的尾插法
void selectSort(Linklist &l){
Linknode *h=l,*p,*pre,*max,*maxpre;
l=NULL;
while(h!=NULL){
p=max=h;
pre=maxpre=NULL; //初始时前驱是空的,因为没有头结点
while(p!=NULL){ //找最大结点
if(p->data>max->data){
max=p;
maxpre=pre;
}
pre=p;
p=p->next;
}
if(max==h) h=h->next; //找了一圈发现最大的就在头上,那么继续对剩下的元素进行选择排序
else maxpre->next=max->next; //否则摘下结点
max->next=l; //链接到已经有序的链表中,此时的待排序列的头由h指向
l=max;
}
}
- 【2】简单选择排序
简单选择排序:保存第一个元素位置,每次找最小值下标,若下标不等则交换位置
插入排序:对1-n排序,将2-n依次插入前面已排好序的子序列中,n-1次操作就能得到有序表
void selectSort(int A[],int n){
for(int i=0;i<n;i++){
min=i;
for(j=i+1;j<n;j++){
if(A[j]<A[min]) min=j;//更新最小元素位置
}
if(min!=i) swap(A[i],A[min]);//与第i个位置交换
}
}
- 【1】直接插入排序
将后n-1个元素插排进前面的表中
void InsertSort(int A[],int n){
int i,j;
for(i=2;i<=n;i++){
A[0]=A[i];
for(j=i-1;A[0]<A[j];--j){
A[j+1]=A[j];
}
A[j+1]=A[0];
}
}
从m+1开始,将后n个元素依次插入前面的有序表中(插入排序)
void Insert_sort(int A[],int m,int n){
int i,j;
for(i=m+1;i<=m+n;i++){ //依次将【m+1…m+n】插入有序表
A[0]=A[i]; //哨兵,
for(j=i-1;A[j]>A[0];j--) A[j+1]=A[j]; //从后往前插入,元素后移,妙在用值来比较
A[j+1]=A[0]; //插入
}
}
- 【3】5.设计算法判断数据序列是否构成小根堆
将顺序表L[1..n]视为完全二叉树,扫描所有分支节点,遇到孩子节点的关键字小于根节点的关键字时返回false,扫描完成后返回true
判断分为有无单分支的情况,有单分支还要单独判断这个单分支(A[len/2]是这个单分支节点的父节点),否则全部一起按双分支判断(判断两个孩子是不是符合堆)
bool IsMinHeap(ElemType A[],int len){
if(len%2==0){ //说明有一个单分支结点
if(A[len/2]>A[len]) return false; //判断单分支结点
for(i=len/2-1;i>=1;i--){ //判断所有双分支
if(A[i]>A[2*i]||A[i]>A[2*i+1]) return false;
}
}
else{
for(i=len/2;i>=1;i--){//判断双分支
if(A[i]>A[2*i]||A[i]>A[2*i+1]) return false;
}
}
return true;
}
各种排序算法的比较和应用 p327 2-3
- 【2】2.顺序表A[]中元素存储在下标1~m+n范围内,两部分都递增有序,设计算法使整个顺序表有序
直接插入排序
从m+1开始,将后n个元素依次插入前面的有序表中(插入排序)
void Insert_sort(int A[],int m,int n){
int i,j;
for(i=m+1;i<=m+n;i++){//依次将【m+1…m+n】插入有序表
A[0]=A[i];//哨兵,
for(j=i-1;A[j]>A[0];j--) A[j+1]=A[j];//从后往前插入,元素后移,妙在用值来比较,用下标比较则是大错特错了
A[j+1]=A[0];//插入
}
}
- 【2】(4)、从串str中的pos位置起,求出与substr串匹配的子串位置
求next时要注意的是,求next数组时,第一位是0和第二位是1 是确定的
kmp的时候要注意的是,题要求从pos开始,所以i从pos开始,且在匹配的过程中均不可超过表长,最后返回的是int类型,返回0表示匹配失败或者返回匹配成功的下标值
void get_next(Str substr,int next[]){
int i=1,j=0; //j是当前子串最长相等前后缀长度
next[1]=0; // 本代码下标是+1的,所以next中也是-1+1=0的,所以这里是指next数组第一个,无条件为0
while(i<substr.length){
if(j==0||substr.ch[i]==substr.ch[j]){
++i;++j;next[i]=j; //next的第二位无条件为1
}
else j=next[j];
}
}
int Kmp(Str str,Str substr,int next[],int pos){
int i=pos,j=1;
while(i<=str.length&&j<=substr.length){
if(j==0||str.ch[i]==substr.ch[j]){
++i;++j;
}
else j=next[j];
}
if(j>substr.length) return i-substr.length; //得到与模式串匹配的子串的首字符位置
else return 0;
}
- 【2】2、采用定长顺序存储表示串,编写函数,删除串中下标为i的字符开始的j个字符,如果第i个字符后没有足够的j个字符,则有几个删除几个
从下标i+j个字符开始,将所有的字符向前移动j个单位,然后将字符串长度缩减为被删除字符的个数即可。
typedef struct{
char ch[maxSize];
int length;
}Str;
int Delete_i(Str &str,int i,int j){
if(i<str.length&&i>=0&&j>=0){ \\范围符合要求
for(int k=i+j;k<str.length;k++){ \\往前搬,把后面需要的部分搬空
str.ch[k-j]=str.ch[k];
}
str.length-=(str.length-i<j?str.length-i:j);
\\更新当前的长度,需要删除的个数超过能删的数量时,需要减去实际删除的个数
}
str.ch[str.length]='\0'; //与顺序表int类型的区别就在这里
}
- 【2】5、构造串的链表结点数据结构,编写函数找出串str1中第一个不在str2中出现的字符
拿串1中的每个字符与2中每个比较,一旦有不同就将标记置为false,一旦标记变成false就返回此时的值
typedef struct SNode{
char data;
struct Str *next;
}SNode;
char findfirst(SNode &str1,SNode &str2){
SNode *p=str1,*q=str2;
while(p){ //在1里面找所以遍历1
bool flag=false;
while(q){ //每一个1中的字符都要扫描一遍2
if(p->data==q->data){ //碰到相同的了就要换下一个1了,要找那个从头到尾一次都没碰到的才对,只要满足一次相等就要直接break出去换下一个p
flag=true;
break;
}
q=q->next;
if(flag==false) return p->data; //一次都没碰到,就是你啦
}
p=p->next;别忘了
}
}