数据结构代码题(入土第34天)结束"/>
数据结构代码题(入土第34天)结束
一. 线性表
1.
在头结点的单链表L中,删除所有值为x的结点,并释放其空间,假设为x的结点不唯一
算法思想:找到前驱,用p遍历整个链表,用q标记找到的元素然后删除q
void Del_x(LinkList &L,Elemtype x)
{LNode *p=L->next,*pre=L,*q;while(P!=null){if(p->data==x){q=p;p=p->next;pre->next=p;free(q);}else//此时p所指结点值不为x,pre和p都后移一位pre=p;p=p->next;}
}
2.
将两个有序顺序表合并成一个新的有序顺序表,并由函数返回结果顺序表
算法思想:比较A和B中较小的元素依次存入C中,若剩余则依次存入C
bool Merge(SqList A,SqList B,SqList &C)
{if(A.length+B.length>C.MaxSize)return false;//用i标记A中第一个元素,j标记B中第一个元素,k标记C中第一个元素int i=0,j=0,k=0;while(i<A.length&&j<B.length){if(A.data[i]<B.data[j]){ C.data[k++]=A.data[i++];}else{ C.data[k++]=B.data[j++];}}//若A中有剩余,将A中元素依次存入B中while(i<A.length){C.data[k++]=A.data[i++];}while(j<B.length){C.data[k++]=B.data[j++];}C.length=k;return true;
}
3.
设计一个高效算法,将顺序表L的所有元素逆置,要求算法的空间复杂度为O(1)
算法思想:将第0个和第n个交换,第1个和第n-1个交换......第i个和第n-1个交换
void Reverse(SqList &L)
{Elemtype temp;for(i=0,i<L.length/2,i++){temp=L.data[i];L.data[i]=L.data[L.length-i-1];//数组从0开始编址,所以数组长度需要减去1,n=length-1L.data[L.length-i-1]=temp;}
}
解法二,递归法
void Reverse(int *A,int low,int high)
{if(low<high)swap(A[low],A[high]);Reverse(A,low+1,high-1);
}
void swap(i,j)
{Elemtype temp;temp=i;i=j;j=temp;
}
4.
对长度为n的顺序表,编写一个时间复杂度为O(n)、空间复杂度为O(1)的算法,该算法删除线性表中所有值为x的数据元素
算法思想:扫描一次顺序表,记录下其中值为x的个数,将其中不是k的元素向前移动k个单位
找到值为x的数据元素,然后进行删除(将后面元素移过来进行覆盖)
void Del_x(SqList &L,Elemtype x)
{while(i<L.length){int k=0;int i=0;if(L.data==x)++k;elseL.data[i-k]=L.data[i];//向前移动k个单位++i;}L.length=L.length-k;
}
方法2
算法思想:遍历顺序表,保留下不是x的值
void Del_x(SqList L,Elemtype x)
{int k=0;for(int i=0;i<L.length;++i){if(L.data!=x){L.data[k]=L.data[i];k++;}
}L.length=k;
}
5. (同T4)
从顺序表中删除其值在给定s和t之间(包含s和t,要求s<t)的所有元素,如果s或t不合理或者顺序表为空,则显示出错信息并退出运行
算法思想:扫描一次顺序表,记录下其中在给定s和t之间(包含s和t,要求s<t)的的个数k,将其中不是k的元素向前移动k个单位
bool delete(SqList &L,ElemType s,ElemType t)
{if(s>=t || L.length==null)return null;int k=0;for(int i=0;i<L.length;++i){ if(L.data[i]>=s && L.data[i]<=t)++k;elseL.data[i-k]=L.data[i];}L.length=L.length-k;return true;
}
6.
从顺序表中删除所有其数值重复的元素,使表中所有元素的值均不相同。
算法思想:第一个元素肯定不重复,依次向后扫描,不重复就保留,重复就保留
用指针i记录需要保留的元素,指针j来遍历表
bool Del_Same(SqList &L)
{if(L.length==null)return null;for(int i=0,j=1;j<L.length;++j){if(L.data[i]!=L.data[j]){//将不重复的值保留下来++i;L.data[i]=L.data[j];L.length=i+1;//i从0开始编址return true;} }
}
7.(3的变题)
已知在一维数组A[m+n]中依次存放两个线性表(a1,a2,…am)和(b1,b2,…bn)。试编写一个函数,将数组中两个顺序表的位置互换,即将(b1,b2,…bn)放到(a1,a2,…am)的前面;
算法思想:将前m个(a1,a2,......am)逆置,将后n个(b1,b2,......bn),再将整个表逆置
void Reverse(L,int 0,int n)
{for(int i=0;i<l.length/2;++i){int temp=L.data[i];L.data[i]=L.data[L.length-i-1];L.data[L.length-i-1]=temp; }
}
void Exchange(A,int n,int m)
{Reverse(A,0,m-1);Reverse(A,m,m+n-1);Reverse(A,0,m+n-1);
}
8.(同T7)
设将n(n>1)个整数存放到一位数组R中,设计一个在时间和空间两个方面都尽可能高的算法,将R中保存的序列循环左移p(0<p<n)个位置,即将R中的数据由(X0,X1,X2,Xn-1)变成(Xp,Xp+1…Xn-1,X0,X1,Xp-1)
算法思想:将前p个(X0,X1,,,,,Xp-1)逆置,将后n-p个(Xp,Xp+1......Xn-1),再将整个表逆置
void Reverse(int R[],int from,int to )
{for(int i=0;i<int(to-from+1/2);++i){ElemType temp=R.[from+i];R[from+i]=R[to-i];R[to-i]=temp;}
}
void Exchange(R,int n,int p)
{Reverse(R,0,p-1);Reverse(R,p,n-1);Reverse(R,0,n-1);
}
8.1
顺序存储结构求子串
void substring(char A[],long start,long count,char &B[])
//char A[]原串,long start开始,long count子串长度,char B[]返回的子串
{long i,j;long len=strlen(A)if(start<0||start>len||start+count-1>len)return -1; //开始位置非法,或者子串长度非法else{for(i=start-1,j=0;i<start+count-1;++i,++j){B[j]=A[i]}B[j]='/0'; //结束标志}
}
二. 链表
9.
尝试编写带头结点的单链表L中删除一个最小值节点的高效算法(假设最小值节点是唯一的)
算法思想:用p遍历链表,用minp标记最小值,找到到最小值;
找到后进行删除,所以不仅要标记最小值节点,还需要标记前驱指针pre和最小值前驱节点minpre
void Del_min(LinkList &L)
{LNode *pre=L,*p=pre->next;Lnode *minpre=pre,*minp=p;while(p!=null){if(p->data<minp->data){minp=p;minpre=pre;}pre=p;p=p->next;}minpre->next=minp->next;free(minp);return L;
}
10.
头插法建立单链表(逆置链表可以使用):输入数据的次序和生成链表的顺序相反
LinkList List_HeadInsert(LinkList &L)
{Lnode *s,int x;L=(Linklist *)malloc(sizeof(Lnode));L->next=null;scanf("%d",&x);while(x!=-1)//选择输入-1作为插入结束标识符{s->data=x;s->next=L->next;L->next=s }return L;
}
11.
尾插法建立单链表:输入数据的次序和生成链表的顺序相同
//重点是标记表尾
LinkList List_TailInsert(LinkList &L)
{Lnode *s,int x;L=(ListList*)malloc(sizeof(LNode));//申请插入节点空间LNode *r=L;//指针r始终指向尾结点;scanf("%d",& x);//键入x 的数值域while(x!=-1){s=(LNode*)malloc(sizeof(LNode));s->data=x;r->next=s;r=s; }r->next=null;//表尾一定要置空return L;
}
12.
试编写算法将带头结点的单链表就地逆置
算法思想:头插法进行逆置,用p遍历标记结点,用r标记p的后续指针防止头插p结点后造成断链
LinkList Reverse_LinkList(LinkList *L)
{LNode *p=L->next,*r;L->next=null;//因为是头插入法,要保证链表的表尾为空while(p->next){r=p->next;//保留后继结点防止断链p->next=L->next;L->next=p;p=r;}
}
方法二:pre用来标记p的前驱,r用来标记p的后继,让p指向pre,然后pre和p,r后移一位继续
LinkList Reverse_LinkList(LinkList &L)
{LNode *pre=L->next;LNode *p=pre->next;LNode *r=p->next;while(r!=null){p->next=pre; //遍历链表将指针逆置pre=p;p=r;r=r->next;}L->next=p;//当r为空时,p指向最后一个结点
}
13.
设在一个带表头结点的单链表中所有元素的结点的数据值无序,试编写一个函数,删除表中所有介入给定的两个值(作为函数的参数给出)之间的元素的元素(若存在)
算法思想:1.遍历链表,找min<x<max
2.删掉x
void Delete(LinkList &L,int min,int max)
{LNode *pre=L;LNode *p=L->next;while(p!=null){if(p->data>min&&p-data<max){pre->next=p->next;free(p);p=pre->next;}else{pre=p;p=p->next;}}
}
14.
给定两个单链表,编写算法找出两个单链表的公共结点(公共结点之后的归并成一条链表)
算法思想:暴力循环法,通过p,q指针分别遍历两个链表,然后一个动一个不动,寻找相同结点,直至两个指针都指向表尾
Ful Search_Common(LinkList &L1,LinkList &L2)
{LNode *p=L1->next;LNode *q=L2->next;while(p!=null){while(q!=null){if(p=q)return p; //找到公共结点,返回elseq=q->next;//p不动,q向后移动}p=p->next; //p向后移动一位q=L2->next; //重置q,进行新一轮的扫描}
}
时间复杂度:O(L1表长*L2表长)
算法思想:(双指针问题)同时到达表尾时,若有公共结点则至少到最后一个结点是公共结点
如果两条链表长度相等,则p和q同时移动即可
若两条链表长度不想等,用较长的链表长度减去较短的链表长度得到差k,然后长链表先移动k位,然后再同步向后移动
LinkList Search_Common(ListList &L1,LinkList &L2)
{int len1=length(L1);int len2=length(L2);//找较长的链表长度if(len1-len2>0){k=len1-len2;p=L1->next; //让p指针始终指向较长的链表q=L2->next;}else{k=len2-len1;p=L2->next;q=L1->next;}while(k--)p=p->next;//让长链表先移动k位while(p!=null){if(p=q) //若相等,则找到了公共结点return p;else //否则,同时向后移动一位{p=p->next;q=q->next;}}
//已经移动到表尾,还没找到表明没有公共结点
return 0;
}
时间复杂度O(L1表长+L2表长)线性复杂度
15.
将一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含有原表中序号为奇数的元素,而B表中含有原表中序号为偶数的元素,且保持其相对顺序不变
算法思想:相对顺序不变:尾插法
用i进行计数,如果i%2==0,偶数,插入B中,否则插入B中
LinkList create(LinkList &A)
{int i=0;B=(LinkList *)malloc(sizeof(LNode));B->next=NULL;LNode *p=A->next;LNode *ra;LNode *rb=B->next;A->next=NULL;while(p!=NULL){if(i%2==0){rb->next=p;rb=p;++i;}else{ra->next=p;ra=p;++i;}p=p->next;}ra->next=NULL;rb->next=NULL;return B;
}
算法思想:将第一个元素插入A,第二个元素插入B,交替插入
LinkList create(LinkList &A)
{B=(LNode*)malloc(sizeof(LNode));B->next=NULL;rb=B->next;ra=A->next;A->next=NULL;while(p!=NULL){//将第一个元素插入A,第二个元素插入B,交替插入ra->next=p;ra=p;p=p->next;if(p->next!=null){rb->next=p;rb=p;p=p->next; }}ra->next=NULL;rb->next=NULL;return B;
}
16.
设C={a1,b1,a2,b2,…an,bn}为线性表,采用带头结点的hc单链表存放,设计一个就地算法,将其拆分为两个线性表,使得A={a1…an},B={bn,bn-1…b1}。
算法思想:A链表采用尾插法,B链表采用头插法
遍历C链表,将奇数号结点尾插在A中,将偶数号结点头插在B中
LinkList create(LinkList &hc)
{LNode *ra=hc;//尾插法的尾指针LNode *p=hc->next;//遍历指针A=(LNode*)malloc(sizeof(LNode));A->next=null;B=(LNode*)malloc(sizeof(LNode));B->next=null;while(p!=null){//尾插法需要用ra标记尾结点ra->next=p;ra=p;p=p->next;if(p->next!=null){//头插法插入B链表r=p->next;//r指针用来防止断链p->next=B->next;B->next=p;p=r;}}ra->next=null;return A;return B;
}
头插防断链,尾插留尾针
17.
在一个递增有序的线性表中,有数值相同的元素存在,若存储方式为单链表,设计算法去掉数值相同的元素,使表中不在有重复的元素
算法思想:递增有序,重复元素必定相邻,只要保留第一个元素,用pre和p的data比较
void Del_Common(LinkList &L)
{LNode *pre=L->next;LNode *p=pre->next; //p是遍历指针while(p!=null){if(pre->data=p->data)//找到相同结点{ pre->next=p->next;//释放相同结点free(p);p=pre->next;} else{pre=p;//如果结点不相同,指针同步向后移动p=p->next;}}
}
18.(同T2)
假设有两个按元素值递增次序排列的线性表,均以单链表的形式存储,编写算法将这两个单链表归并为一个按照元素次序递减排列的单链表,并要求利用原本的单链表的结点存放归并后的单链表
算法思想:递增变递减,考虑用头插法,用p和q遍历两个链表,比较pq的数值的大小,将小的头插入合并链表,直到其中一条为空,将非空的结点依次以头插法插入合并链表
LinkList merge(LinkList A,LinkList B,LinkList &C)
{LNode *p=A->next;LNode *q=B->next;C = A;C->next = NULL;free(B);while(p!=null&&q!=null){if(p->data<=q->data) //如果p的数值小于q则头插入合并链表{r=p->next; //指针r用来防止断链p->next=C->next;C->next=p;p=r;}else{r=q->next;q->next=C->next;C->next=q;q=r;}}if(q!=null) //若B有剩余,则将B中结点全部头插入合并链表{r=q->next;q->next=C->next;C->next=q;q=r;}else //若A有剩余,则将A中结点全部头插合并链表{r=p->next;p->next=C->next;C->next=p;p=r;}return C;
}
18.5
设顺序表用数组A[]表示,表中元素的存储范围为1-m+n,前m个元素递增有序,后n个元素递增有序,设计一个算法,使整个顺序表有序。
算法思想:取表中A[m+1]存入辅助变量temp中,让temp逐个和A[m]A[m-1]....A[1]进行比较,当temp<A[j]时,将A[j]后移一位;否则将temp存入A[j]
实际就是不断将后n个元素不断插入前m个元素的过程。
void Insert(int A[],int m,int n)
{int i,j;int temp;for(i=m+1;i<=m+n;i++){temp=A[i];for(j=i-1;j>=1&&temp<A[j];--j){A[j+1]=A[j];//j后移一位}A[j+1]=temp;}
}
19.
设A和B是两个单链表,带头结点,其中元素递增有序,设计一个算法从A和B的公共元素产生单链表C,要求不破坏A和B的结点
算法思想:要求不破坏结点,则需要重新申请结点。
比较A和B的结点元素数值,将元素值小的指针往后移动,一旦两个相等,则说明是公共值,创建一个新结点值等于此数值,没做要求,用尾插法,并将两个同时向后移动,如果其中一个结束,则算法结束,因为余下的未遍历的结点值中已经不可能有公共元素
LinkList Common(LinkList A,LinkList B)
{LNode *p=A->next;LNode *q=B->next;LinkList C=(LinkList)malloc(sizeof(LNode));C->next=null;LNode *r=C->next;while(p!=null&&q!=null){if(p->data<q->data) //如果p的数值小,则后移一位{p=p->next;}else if(p->data>q->data){q=q->next;}else if(p->data==q->data){s=(LNode*)malloc(sizeof(LNode));s->data=p->data; //若相等,申请新结点,将相等的值赋值给新结点,然后将新结点尾插进r->next=s;r=s;p=p->next; //遍历指针同步向后移动一位q=q->next;}}r->next=null;return C;
}
20. (同T19)
已知两个链表A和B分别表示两个集合,其元素递增有序排列,编写函数,求A和B的交集,并存放于A链中
算法思想:A中只能剩交集元素。
依次扫描AB结点,一边扫描结点的data域值,将较小的元素值指针向后移动(移动时释放该空间),若两者相等,就尾插在A链表中,直至遍历到A或者B的表尾,若A中还有剩余,则逐个释放剩余元素(只保留公共空间即可,其他结点释放空间)
LinkList Common2(LinkList &A,LinKList &B)
{ LNode *p=A->next;LNode *q=B->next;A->next=null;LNode *r=A;LNode *u;while(p!=null&&q1!=null){if(p->data<q->data)//谁小释放谁{u=p;p=p->next;free(u);}else if(p->data>q->data){u=q;q=q->next;free(u);}else if(p->data==q->data){r->next=p;r=p; //将相等的元素尾插入Ap=p->next;u=q;q=q->next;free(u);}}while(p!=null)//若A中有剩余,则释放空间{u=p;p=p->next;free(u);}r->next=null;return A;
}
21.
两个整数序列A=a1,a2,a3…am和B=b1,b2,b3,…bn已经存入两个单链表中,设计一个算法,判断序列B是否是序列A的连续子集
算法思想:
暴力法:用p和q遍历A和B,若pq指针数值域相同,则同步后移;若不等,则p重置回初始位然后后移一位(需要pre指针辅助重置标记位),q重置回初始位,再同步移动,
直到B链表到末尾,表示匹配成功,
若A链表到尾,而B未到末尾,则匹配失败
bool Son_(LinkList A,LinkList B)
{LNode *p=A->next;LNode *q=B->next;LNode *pre=p;while(p!=null&&q!=null){//若不等,则p重置回初始位然后后移一位(需要pre指针辅助重置标记位),q重置回初始位if(p->data!=q->dat){pre=pre->next;p=pre;q=B->next;} //相等则同步后移else {p=p->next;q=q->next;}}if(q==null)//B链表到末尾,表示匹配成功,return true;else return false;
}
22.
设计一个算法判断带头结点的循环双链表是否对称
算法思想:让p从左往后扫描,q从右向左扫描,直到他们指向同一结点,相遇或者相邻返回1表示是对称双链表
typedef struct DNode{ElemType data;struct DNode *pre,*next;
} int Symmetry(DLinkList L)
{DNode *p=L->next;DNode *q=L->prior;while(p!=q&&q->next!=p) //相等或者相邻{if(p->data==q->data){p=p->next;q=q->prior;}elsereturn 0;}return 1;
}
23.
有两个循环单链表,链表头指针分别为h1和h2,编写一个函数将链表h2连接到h1之后,要求链接后的链表任保持循环链表的形式
算法思想:找到h1的表尾p和h2的表尾q,将p指向h2,q指向h1
LinkList Link(ListList &h1,ListList &h2)
{LNode *p=h1->next;LNode *q=h2->next;while(p->next!=h1){p=p->next;}while(q->next!=h2){ q=q->next;}p->next=h2;q->next=h1;
}
24.(同T9)
设有一个带头结点的循环单链表,其结点值均为正整数,设计一个算法,反复找出单链表中结点最小的值并输出,然后将该结点从中删除,直到单链表为空位置,再删除表头结点
算法思想:反复找出当前的最小值结点,并删除直至链表为空,释放头结点L
void Del(CLinkList &L)
{LNode *p,*pre,*minp,*minpre;while(L->next!=L) //遍历整个链表进行最小值删除{//反复重置然后找新最小值pre=L;p=L->next;minp=p;minpre=pre;while (p!=L) //找到最小值结点,输出值并删除{if(p->data<minp->data){minp=p;minpre=pre;}else{pre=p;p=p->next;}}printf("%d",minp->data);minpre->next=minp->next;free(minp);}free(L);
}
25.
设头指针为L的带有表头结点的非循环双向链表,其每个结点中除有pred,data和next域外,还有一个访问频度域freq。在链表被使用前,其值均初始化为0,每当在链表中进行依次Locate(L,x)运算时,令元素值为x的结点中的freq域的值增加1,并使此链表中结点保持按频度非(递减)的顺序排列,同时,最近访问的结点排在频度相同的结点前面,以便使频繁访问的结点总是靠近表头
算法思想:找到含x的结点;使其freq++;取下该x结点;根据freq的大小插入第一个比他大的结点后面;
操作:取下结点,插入结点
DLNode Locate(DLNode &L,int x)
{DLNode *p=L->next;DLNode *q=L->pred;while(p!=null&&p->data!=x) //查找x的结点指针p=p->next;if(p==null){ printf("不存在%d的结点",x)exit(0);}else //找到了x的值{p->freq++; //freq加一if(p->next!=null) //取下该结点{p->next->pred=p->pred;p->pred->next=p->next;}//查找p的插入位置while(q!=L&&q->freq<=p->freq) //因为q是往前回溯所以终止条件是找到头结点q=q->pred;//将p插入到合适的位置p->next=q->next;q->next->pred=p;p->pred=q;q->next=p; }return p;
}
26.
已知一个带有表头结点的单链表,结点的结构为data link,假设该链表只给出头指针list,在不改变链表的前提下,设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)若查找成功,算法输出该结点data域的值,并返回1,否则返回0
算法思想:找出倒数第k个位置;
1.遍历一次链表得到length,第二次遍历length-k得到所求结点(浪费时间)
2.申请一个顺序表作为辅助空间,将链表存入数组中,然后直接访问第k下标的元素(浪费空间)
3.先将p移动到第k个位置,p,q一起向后移动,直至p到达表尾,则此时q所指向的结点为所求结点(利用双指针中“间隔一定,同步后移”)
int Search_k(LinkList L,int k)
{LNode *p=L->next;//pq指向第一个结点LNode *q=L->next;int count=0;
while(p!=null)
{ //count<k,只执行p=p->link;即 将p移动到第k个位置if(count<k)count++; else //如果count=k,则q开始和p同步向后移动q=q->link;p=p->link; //无论无何,这句都在执行if(count<k) //k非法则返回0return 0;else //找到了则输出q的值printf("%d",q->data);return 1; }
}
27.(同T14)
假定采用带头结点的单链表保存两个单词有相同的后缀时,可享受相同的后缀存储空间,设str1和str2分别指向两个单词的单链表的头结点,链表结点结构为data,next 请设计一个在时间上尽可能高的算法,找出str1和str2所指向的两个链表共同后缀的起始位置
算法思想:暴力法:双重循环
遍历一次,间隔一定,同步后移,(双指针问题)
求出str1和str2所指链表的长度m和n
用较长的链表长度减去较短的链表长度得到差k,然后长链表先移动k位,
然后再同步向后移动,直至pq指向同一位置
//求链表的长度
int Len(LNode *head)
{int len=0;LNode *p=head->next;while(p!=null){len++;p=p->next;}return len;
}
LNode Fun_search_common(LinkList str1,Linklist str2)
{int m=Len(str1);int n=Len(str2);LNode *p,*q;for(p=str1->next;m>n;m--) //若m大,则q先移动m-k个位置p=p->next;for(q=str2->next;m<n;n--) //若n大,则q先移动m-k个位置q=q->next;//同步向后移动while(p!=null&&q!=q){p=p->next;q=q->next;}if(p==null)return 0;elsereturn p;
}
28.
用单链表保存m个整数,结点的结构为data link,且data的绝对值是小于等于n的正整数,现要求设计一个时间复杂度尽可能高效的算法,对于链表中的绝对值相等的结点,仅保留第一次出现的结点,而删除其余绝对值相等的结点。
算法思想:时间上尽可能高效(暗示:以时间换空间),可以考虑开辟一个辅助数组,申请一个n+1辅助空间(0-n),
初始为0,
第一次出现,保留(将该值对应的数组中的下标从0变为1),否则删除结点;第一次出现的保留,其余删除(绝对值相同)
考虑标记数组,将链表中的值转换为数组中的次序,从而达到标记的目的
void Func(LinkList &head,int n)
{LNode *pre=head, *p=pre->link;c=(int)malloc(sizeof(int)*(n+1));for(int i=0;i<n+1;++i)c.data[i]=0;//将数值转换为次序达到标记数组的目的while(p!=null)m=p->data>0?p->data:-p->data;//取p的绝对值if(c.data[m]==0)//若标记为0,则是第一次出现,需要保留 {c.data[m]=1; //标记为1pre=p;p=p->link; //保留向后移位}else{pre->link=p->link;free(p);}free(c);//释放辅助数组
}
时间复杂度 O(m)
空间复杂度O(n+1)、
29.
设线性表L=(a1,a2,a3…an)采用带头结点的单链表存储,链表的结点定义如下
Typedef struct node
{int data;struct node *next;
}NODE;
设计一个空间复杂度为O(1)且时间上尽可能高效的算法,重新排列L中的各结点,得出线性表L1=(a1,an,a2,an-1…)。
算法思想:空间O(1)不允许申请额外空间
将后半段链表逆置,依次插入L的前半段
1.如何找到单链表的中间结点?:设置两个指针,(间隔一定,同步后移),q每次移动两次,p移动一次,则当p到达表尾时,q到达链表的中间位置(同T14)
2.后半段逆置(头插法)
3.合并(尾插法)
void change(NODE *L)
{NODE *p,*q,*r,*s;p=L->next;q=L->next;while(q->next!=null) //寻找中间结点{p=p->next; //p走一步q=q->next; //q走两步if(q->next!=null)q=q->next;}q=p->next; //p所指的结点为中间结点,q重置为后半段链表的首结点p->next=null; //断开 后续进行头插法逆置while(q!=null){//将q头插到p之后r=q->next;q->next=p-next;p->next=q;q=r;}s=L->next; //s指向前半段的第一个数组结点,即插入点q=p->next; //q指向后半段的第一个数据结点p->next=null;while(q!=null) //将链表的后半段结点插入到指定的位置{//将q依次插入到s之后r=q->next; //防止断链q->next=s->next;s->next=q;s=q->next; //更改s的位置q=r; //防止断链}
}
30.
总结
- 头插法 头插防断链
- 尾插法 尾插留尾针
- 逆置法(顺序表折半交换;链表头插或者换方向)
- 归并法(两种情况,一种允许破坏链表结构,一种不允许)
- 双指针法(取较小,倒数第k个元素(一快一慢),中间元素)
- 双循环链表(插入和删除)
终于结束链表了,如果你看到这,已经快结束了哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
三. 树和二叉树
31.
已知一颗二叉树按顺序存储结构进行存储,设计一个算法,求编号分别为i和j的两个结点的最近公共祖先结点的值
算法思想:
顺序存储结构的适用范围:完全二叉树和满二叉树;
对于普通二叉树,可用空指针结点补充转换为完全二叉树;
顺序存储二叉树,数组从1开始标号;
不断的将较大的取一半,比较i和j的大小,直到i=j,表明找到了公共祖先结点
elemtype Com_Ancestor(SqTree T,int i,int j)
{if(T[i]!=null&&T[j]!=null){while(i!=j){if(i>j)i=i/2;elsej=j/2;}}return T[i];
}
32.
二叉树的遍历,先序、中序、后序
算法思想:
visit的位置;
递归:在函数体内调用自身
//二叉树的链式存储结构
typedef struct BiTNode{elemtype data;struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;//先序遍历
void preorder(BiTree T)
{if(T!=null){visit(T);preorder(T->lchild);preorder(T->rchild);}
}
//中序
void inorder(BiTree T)
{if(T!=null){inorder(T->lchild);visit(T);inorder(T->rchild);}
}//后序
void postorder(BiTree T)
{if(T!=null){postorder(T->lchild);postorder(T->rchild);visit(T);}
}//总结
void Funorder(BiTree T)
{if(T!=null){//操作Funorder(T->lchild);//操作Funorder(T->rchild);//操作}
}
无论采用哪种遍历算法,所有结点都访问一次且仅访问一次,时间复杂度为O(n),最坏的空间复杂度为单分支树,为O(n)
33.
递归与非递归的转换问题
因为实际执行过程时,效率低
中序的非递归算法
算法思想:
1.沿着根的左孩子,依次入栈(先进先出)直至左孩子为空,
2.将栈顶元素出栈并访问,若出栈的结点的右孩子也为空,则继续出栈栈顶元素并访问
3.否则则执行1
void inorder2(BiTree T)
{InitStack(s);BiTree p=T;while(p||!IsEmpty(s)){if(p!=null){push(s,p); //一路向左走p=p->lchild; //左孩子不空,则一直向左走}else{pop(s,p); //左孩子为空进入else栈顶元素出栈visit(p); //访问出栈元素p=p->rchild; //向右孩子走}}
}
入栈向左一直走,出栈访问右子树
33.5
先序遍历非递归算法
算法思想:
1.入栈元素,栈不空则出栈访问,并访问其左右孩子
2.若左右孩子存在,则右孩子入栈,左孩子后入栈
3.若没孩子则继续出栈一个元素
直至栈为空
void preorder2(BiTree T)
{InitStack(s);BiTree p=T;push(s,p); //先入栈一个元素while(!IsEmpty(s)) //栈不空{push(s,p); //出栈一个元素并对其进行访问visit(p);if(p->rchild!=null) //右孩子不空入栈{p=p->rchild;push(s,p); }if(p->lchild!=null) //左孩子不空入栈{p=p->lchild;push(s);} }
}
入栈一个立刻出
访问判定右先左
34.
后序遍历的非递归算法
算法思想:
1.沿着根的左孩子,依次入栈,直至左孩子为空
2.读栈顶元素(进行判定),若其右孩子不空且未被访问过,将右子树进行相同12处理
3.栈顶元素出栈
void postorder2(BiTree T)
{InitStack(s);r=null; //用来标记最近访问过的结点if(p){push(s,p);p=p->lchild;}elseGetTop(s,p);if(p->rchild!=null&&p->rchild!=r) //r标记最近访问的结点{p=p->rchild; //若右子树不空且未被访问,则入栈向左一直走 push(s,p);p=p->lchild;}else{pop(s,p); //若右子树为空或被访问过,则出栈visit(p->data);r=p; //标记p=null;}
}
入栈向左一直走,判定(右子树),出栈访问 标记,重置
35.
二叉树的层次遍历算法
算法思想:利用队列
1.将根结点入队,出队,访问出队的结点,若它有左孩子,则将左孩子入队,若它有右孩子,则将右孩子入队
2.然后出队,接着判定
直至队为空
void levelOrder(BiTree T)
{InitQueue(Q); //初始化队列BiTree p=T;EnQueue(Q,T); //将根结点入队while(!isEmpty(Q)) //队不空则重复进{DeQueue(Q,p); //出队visit(p);if(p->lchild!=null) //若有左孩子则左孩子入队EeQueue(Q,p->lchild);if(p->rchild!=null) //若有右孩子则右孩子入队EeQueue(Q,p->rchild);}
}
入队出队访问,有左入左,有右入右
36.
试给出二叉树自下而上,从右到左的层次遍历算法
算法思想:利用原本的层次遍历算法,出队的同时将各结点入栈再依次出栈
void levelOrder2(BiTree bt)
{InitQueue(Q);InitStack(S);BiTree p=bt;if(bt!=null) //确保树非空树{EnQueue(Q,T); //入队while(!isEmpty(Q));{DeQueue(Q,p); //出队push(S,p); //将原本需要出队访问的元素,压栈if(p->lchild!=null)EnQueue(Q,p->lchild);else if(p->rchild!=null)EnQueue(Q,p->rchild);}//当栈不为空,进行出栈操作while(!isEmpty(S)){pop(S,p);visit(p);}}
}
37.
假设二叉树采用二叉链表存储结构,设计一个算法求二叉树的高度(递归和非递归)
递归的算法思想:递归求解左子树高度和右子树高度,取较大者加1
int Btdepth2(BiTree T)
{if(T==null)return 0; //空树高度为0ldep=Btdepth2(T->lchild); //调用函数求左子树高度rdep=Btdepth2(T->rchild); //求右子树高度if(ldep>rdep)return ldep+1;elsereturn rdep+1;
}
非递归的算法思想:层次遍历的改进
设置level记录二叉树的层数,设置指针last指针指向最右结点,只要当fast==last时即可
int Btdepth(BiTree T)
{if(!T)return 0;int front=-1,rear=-1;int last=0,level=0;BiTree Q[MaxSize];Q[++rear]=T; //根结点入队Bitree p;while(front<rear){p=Q[++front]; //将p出队if(p->lchild!=null);Q[++rear]=p->lchild;if(p->rchild!=null)Q[++rear]=p->rchild;if(front==last) //last始终指向最右结点{level ++; last=rear;}}return level;
}
38.
设树B是一颗采用链式存储的二叉树,编写一个把树中所有结点的左右子树交换的函数。
算法思想:递归的交换左右子树
void swap(BiTree b)
{if(b){swap(b->lchild); //递归的交换左子树swap(b->rchild); //递归的交换右子树temp=b->lchild;b->lchild=b->rchild;b->rchild=temp;}
}
39.
假设二叉树采用二叉链表存储结构存储,设计一个算法,计算一颗给定二叉树的所有双分支的结点个数
算法思想:递归,看其本身是空、度为1、度为2
int DoubleNodes(BiTree b)
{if(b==null)return 0;else if(b->lchild!=null&&b->rchild!=null)return DoubleNodes(b->lchild)+DoubleNodes(b->rchild)+1;//加一是加其本身else //b->lchild==null||b->rchild==null 本身是度为1的结点return DoubleNodes(b->lchild)+DoubleNodes(b->rchild);
}
39.5
统计二叉树中度为0的结点个数
int ZeroNodes(BiTree b)
{if(b==null)return 0;else if(b->lchild==null&&b->rchild==null)return ZeroNodes(b->lchild)+ZeroNodes(b->rchild)+1;//加一是加其本身else //b->lchild!=null||b->rchild!=null 本身是度为1的结点return ZeroNodes(b->lchild)+ZeroNodes(b->rchild);
}
统计二叉树中度为1的结点个数
int OneNodes(BiTree b)
{if(b==null)return 0;else if((b->lchild!=null&&b->rchild==null)||(b->lchild==null&&b->rchild!=null))return OneNodes(b->lchild)+OneNodes(b->rchild)+1;//加一是加其本身else //b->lchild!=null||b->rchild!=null 本身是度为1的结点return OneNodes(b->lchild)+OneNodes(b->rchild);
}
40.
计算二叉树中所有结点的个数
递归法算法思想:F(n)=F(左子树)+F(右子树)+1
int count(BTNode *p)
{int n1,n2;if(p==null)return 0;else{n1=count(p->lchild);n2=count(p->rchild);return n1+n2+1;}
}
算法思想:遍历法,以先序为例中设置一个计数
int n=0;
int count(BTNode *p)
{if(p!=null){++n; //将访问p改写成计数加一count(p->lchild);count(p->rchild);}
}
41.
假设二叉树采用二叉链表存储结构存储,设计一个算法,求先序遍历序列中第k(1<=k<=二叉树中结点个数)个结点的值
算法思想:先序遍历二叉树,求第k个结点
int trave(BTNode *p,int k)
{int n=0;if(p!=null){++n; //计数if(k==n) //在先序遍历计数的基础之上加一个判断语句return print("%d",p->data);trave(p->lchild,k);trave(p->rchild,k);}
}
42.
计算二叉树中所有叶子结点的个数
算法思想:递归求解
int count(BTNode *p)
{int n1,n2;if(bt=null)return 0;if(p->lchild==null&&p->rchild==null)return 1;n1=count(p->lchild);n2=count(p->rchild)return n1+n2;
}
算法思想:遍历法(以先序遍历为例)
int count2(BTNode *p)
{int n;if(p!=null){if(p->lchild==null&&p->rchild==null) //visit(p);++n;count2(p->lchild);count2(p->rchild);}
}
43.
查找二叉树中data域等于key的结点是否存在,若存在,则q向它,否则q为空
算法思想:建立在遍历的基础上,在遍历的过程中,寻找符合条件的点
void fun(BTNode *p,BTNode *q,int key)
{q=null;if(p!=null){if(p->data==key)q=p;else{fun(p->lchild,q,key);fun(p->rchild,q,key);}}
}
44.
利用结点的右孩子指针将一个二叉树的叶子结点从左向右链接成一个单链表(head指向第一个,tail指向最后一个)
算法思想:
1.找到叶子节点
2.连成单链表
void link(BT *p,BT *head,*tail )
{if(p!=null){if(p->lchild=null&&p->rchild==null)if(head==null) //初始head为空,要将第一个叶子结点存入{head=p;tail=p;}elsetail->rchild=p; //后面陆续链接就可,tail->rchild=p;相当于tail->next=p;tail=p;}link(p->lchild,head,tail); link(p->rchild,head,tail);
}
45.
若二叉树采用链式存储结构,设求出指定结点在给定二叉排序树的层次
算法思想:如果根结点不为空,则将指定结点的数和树中的结点值作比较,用t遍历,用n记录层次
int level(BTNode *bt,BTNode *p)
{BTNode *t=bt;int n=0;if(bt!=null){while(t->data!=p->data){if(t->data<p->data) //比p所指的数向左找t=t->lchild;else //否则比p大,则往右子树找t=t->rchild;++n; //没走一步层次n加一}}return n;
}
46.
二叉树的带权路径长度(WPL)是二叉树中所有叶节点的带权路径之和,给定一颗二叉树T,采用二叉链表存储,结点结构为 left,weight,right 其中叶节点的weight域保存该结点的非负权值,设root为指向T的根节点的指针,请设计求T的WPL的算法
算法思想:将所有叶子结点的WPL累加就可以了,采用先序递归
1.找叶子结点
int WPL_preorder(BiTree root,int deep)
{int WPL=0;//visit(p);if(root->lchild==null&&root->rchild==null) //若为叶节点,则累计WPLWPL=WPL+deep*root->weight;//preorder(T->lchild);if(root->lchild!=null) //若左子树不为空,则左子树递归遍历WPL_preorder(root->lchild,deep+1);//因为往下走一层,所有层数deep需要加一//preorder(T->rchild);if(root->rchild!=null)WPL_preorder(root->rchild,deep+1);return WPL;
}
int WPL(BiTree root)
{return WPL_preorder(root,0); //从深度0开始调用
}
47.
请设计一颗算法,将给定的表达式(二叉树)转换为等价的中缀表达式(通过 括号反应操作符的计算次序)并输出
//二叉树结点的定义
typedef struct node
{char data[10];struct node *left,*right;
}BiTree;
算法思想:
1.使用中序
if(p!=null)
{//Inorder(T->lchild);//visit(T);//Inorder(T->rchild);
}
2.括号如何加——
①根结点不加括号
②叶子结点不加括号
③其他结点都需要加,中序访问左子树之前加,中序访问右子树之后加
void Inorderfun(BiTree *root,int deep)
{if(root==null)return;else if(root->lchild==null&&root->rchild==null) //叶子结点则直接输出,不用加括号printf("%s",root->data); else{if(deep>1) //说明有孩子属于非叶子结点则需要加上括号,中序访问左子树之前加括号{printf("(");} Inorderfun(root->left,deep+1); //层次加一Inorder(T->lchild);printf("%s",root->data); //visit(T);Inorderfun(root->right,deep+1) //Inorder(T->rchild);if(deep>1) //中序访问右子树之后加括号{printf(")");}}
}
void fun(BiTree *root)
{Inorderfun(root,1); //根结点高度层次为1
}
48.
假设二叉树采用二叉链表结构存储,设计一个算法,求二叉树中值为x的层号
算法思想:
1.使用遍历
2.用h记录层数,遍历时设计加一和减一操作
当访问一个新的孩子结点时需要加一,当从所访问孩子结点回到父节点时需要减一
int h=1;
void fun(BTNode *p,int x)
{if(p!=null){if(p->data==x)print("%d",h);++h; //访问一个新的孩子结点时需要加一fun(p->lchild,x); fun(p->rchild,x);--h; //从所访问孩子结点回到父节点时需要减一}
}
49.
请设计一个算法,增加一个指向双亲结点的parent指针,并给指向的指针赋值,并输出所有结点到根结点的路径
算法思想:
1.增加parent方便找父亲;
2.先解决单个结点到根结点
3.遍历整个树,反复递归第二步
//增加parent指针
void fun(BTNode *p,BTNode *q)
{q=null;if(p!=null) {p->parent=q;q=p;fun(p->lchild);fun(p->rchild);}
}
//单个结点到根结点的路径
void printpath(BTNode *p)
{while(p!=null){print("%c",p-data);p=p->parent;}
}//遍历整个树,输出所有路径
void allpath(BTNode *p)
{if(p!=null){printpath(p); //visit(p)allpath(p->lchild);allpath(p->rchild);}
}
50.
已知一颗二叉树链式存储结构,请设计一个算法,输出根结点到每个叶子结点的路径
算法思想:
1.使用先序遍历进行(中序和后序也可以)
2.对特殊结点(叶子结点)处理
1.使用栈实现
void path(BTNode *p)
{int i=0,top=0; //最开始栈在0,所以可以先入栈再移动指针ElemType Stack[maxSize]; //初始化栈if(p!=null) //不空,结点值入栈{Stack[top]=p->data; //入栈元素++top;}if(p->lchild==null&&p->rchild==null) //叶子结点需要读出栈数据(不是出栈){for(int i=0;i<top;++i) //从栈底开始读printf("%c",Stack[i]);}path(p->lchild); //遍历左子树path(p->rchild); //遍历右子树--top; //每次访问完右子树后要进行指针后退,退回其父节点
}
四. 图
- BFS
- DFS
- 拓扑排序
- 最短路径
- 关键路径
51.
广度优先遍历(BFS) 类似于层次遍历
算法思想:
- 找到与一个顶点相邻的顶点;
- 标记哪些顶点被访问过(visited[]数组);
- 需要一个辅助队列;
- 图的基本操作;- FirstAdjVex(G,v):求图G中顶点的第一个邻接点,若存在,则返回顶点号,若v不存在邻接点或者图中不存在x,则返回-1;- NextAdjVex(G,v,w):图G中顶点v在w之后的下一个邻接点,若存在,则返回顶点号,若v不存在或者图中不存在x,则返回-1。
void BFS(Graph G,int v) //图G,开始顶点v
{InitQueue(Q); //初始化队列for(int i=0;i<G.vexnum,++i) //初始化标记访问数组visited[i]=0;visit(v);//访问初始顶点visited[v]=1; //访问位标记为1EnQueue(Q,v); //将v入队while(!isEmpty(Q)) //当队不空 出队{DeQueue(Q,v);}for(w=FirstAdjVex(G,v);w>=0;w=NextAdjVex(G,v,w)) //检测v的所有邻接点{if(visited[w]==0) //未被访问则访问{visit(w); //访问visited[w]=1; //标记EnQueue(Q,w); //入队}}
}//以下不做要求,可直接使用函数
//求图G中顶点的第一个邻接点,若存在,则返回顶点号
int FirstAdjVex(Graph G,int v)
{int i;for(i=0;i<G.vexnum;++i) //G.vexnum顶点数{if(G.arcs[v][i].adj) //arcs邻接矩阵中v行i列的权值(不为空即v和i存在边){return i;}}return -1; //没有邻接点返回-1
}//图G中顶点v在w之后的下一个邻接点,若存在,则返回顶点号
int NextAdjVex(Graph G,int v,int w)
{int i;for(i=w+1;i<G.vexnum;++i){if(G.arcs[v][i].adj==1){return i;}}return -1;
}
52.
深度优先算法(DFS)类似于先序遍历
算法思想:
1.递归算法,时间复杂度:O(顶点数)
时间复杂度:邻接矩阵O(v^2)
邻接矩阵O(v+e);
2.需要visited数组标记访问位
3.需要栈的辅助一条路走到底,完成回溯
void DFS(Graph G,int v) //传入参数,图G和v,从顶点v出发
{visit(v);visited[v]=1;for(w=FirstAdjVex(G,v);w>=0;w=NextAdjVex(G,v,w)) //检测到v的邻接点{if(visited[w]==0) //邻接结点未被访问{DFS(G,w);}}
}
53.
图的邻接表的存储结构定义
//图的邻接表存储结构
//顶点表
typedef struct VNode
{VexType data; //顶点表的数值ArcNode *firstarc; //取顶点引出的第一条边
}VNode;//边表
typedef struct ArcNode
{int adjvex; //邻接顶点,就是这条边所对应的顶点struct ArcNode *nextarc; //指向下一个边结点的指针
}ArcNode;//图结构体
typedef struct //图结构体
{VNode adjList[maxSize]; //将图的顶点数据放在一个数组中int vexnum, arcnum; //顶点和边的个数
}Graph;//应用
k=p.adjvex;//取p指针指向的结点的值
p=G->adjList[k].firstarc;//取第k个结点的第一条指针
54.
拓扑排序-判断有向图是否存在回路
算法思想:
1、选择一个入度为0的顶点并输出(不唯一
2、从图中删除入度为0的顶点并输出
3、循环进行12,循环结束时,如果输出的顶点数小于图中顶点数,则存在回路,否则,输出的顶点数为拓扑排序的序列
bool TuoPuSort(Graph G)
{InitStack(S); //初始化栈for(int i=0;i<G.Vexnum;++i) //选择入度为0的顶点入栈{ if(indegree[i]==0) //入度为0push(S,i); }int count=0;while(!isEmpty(S)) //栈不空则出栈{pop(S,i)print(count)=i;count++;}for(p=G.vertices[i].firstarc;p!=null;p=p->nextarc) //(邻接表中i顶点引出的第一条边;p不为空;p=p引出的边指向下一个边结点的指针){v=p->adjvex;if(indegree[v]==0) //入度为0的顶点则入栈push(S,v);}if(count<G.vexnum) //有回路return false;else //拓扑排序成功retrun true;
}
55.
快速排序
算法思想:分治法
1.首先写一轮排序的代码
2.反复递归(分治思想)
//一轮快排
int pivot(ElemType A[],int low,int high)
{//将基准元素保存下来temp=A[low];while(low<high){while(A[high]>=temp&&low<high) //从high开始,找比temp小的元素--high;A[low]=A[high];while(A[low]<temp&&low<high) //从low开始,找比temp大的元素++low;A[high]=A[low];}A[low]=temp;return low;//返回最终枢轴所在位置,以便下一轮
}
//反复递归(分治思想)
void QuickSort(ElemType A[],int low,int high)
{if(low<high){int temp=pivot(A,low,high); //因为返回了最终枢轴的位置,所以就得到了第二轮划分的位置QuickSort(A,low,temp-1);QuickSort(A,temp+1,high);}
}
1.
分别用递归和非递归,实现一个正整数字符串转化为相应的整数
//非递归:
int StringToInt(char *ch[])
{int sum=0; //返回的十进制数值int i=0; //用于遍历位数int forExp=1; //用于控制数级while(ch[i]!='\0') //找到字符的末尾位数++i;--i; //减去结束标志位while(i>0) //从第一位开始转化{sum+=(ch[i]-'0')*forExp; //ch[i]-'0':将字符转化为十进制数码forExp=forExp*10;--i;}return sum;
}
//递归
int StringToInt(char *ch[],int L,int R) //把下标L和R之间的数转化为十进制数
{if(L>R) //L和R非法return -1;if(L==R)return ch[R]-'0';elsereturn StringToInt(ch,L,R-1)*10+ch[R]-'0';
}
2.
给定一字符串,该字符串中存在若干相同的字符,设计一个在时间和空间上尽可能高效的算法,找出一对相同字符在该字符串中的最大距离,例如“KLabcLdecL",其中第一个L和最后一个L相距最远
算法思想:
第一次出现,保留(将该值对应的数组中的下标从0变为改字符的ASCII值)作为第一次出现的位置,否则计算距离并和max做比较;
int getMaxLength(char str[],it n) //n为数组长度
{int max=0;int i;int isChInArray[128];for(i=0;i<128;++i)isChInArray[i]=0;//初始化标记数组for(i=0;i<n;++i){if(isChInArray[i]==-1) //未被访问isChInArray[i]=i;else //被访问过需要计算max{int tempL=i-isChInArray[str[i]]; //计算相对距离if(max<tempL)max=tempL;}}return max;
}
更多推荐
数据结构代码题(入土第34天)结束
发布评论