❀本篇是关于计算机考研大师兄每日算法学习笔记
【算法思想】:
删除值为x的结点,首先需要找到其前驱结点。
通常需要用到3个指针
pre | 用于指向前驱 |
---|---|
p | 用于遍历整条链表 |
q | 指向当前找到的值为x的结点 |
void Del_x(LinkList &L,Elemtype x){
//指针初始化
Lnode *p=L->next;
Lnode *pre=L;
Lnode *q;
while(p!=NULL){
if(p->data==x){
q=p;
p=p->next;
pre->next=p;//删除掉q结点后,防止断链
free(q);
}
else{ //即当前p所指的结点的值不为x,则两个结点的值都需要往后移动一位
pre=p;
p=p->next;
}
}
}
注意:
时间复杂度为O(n)即 只能扫描1次顺序表;
空间复杂度为O(1)即 只能申请常数个辅助空间。
基础分析:
①【算法思想】:
遍历1次顺序表,将其中 x 的个数 记录为k,并将不为x的元素向前移动k个单位
void Del_x(sqList &L,ElemType x){
int k=0;
int i=0;
for(i=0;i<L.length;i++){
if(L.data[i]==x){
k++;
}
else{ //即不为x的元素
L.data[i-k]=L.data[i]; //将不为x的元素向前移动k个单位
}
}
L.length=L.length-k;
}
②【算法思想】:
遍历顺序表时,保留不是x的值。(即k记录的是保留的个数)
void Del_x(sqList &L,ElemType x){
int k=0;
int i=0;
for(i=0;i<L.length;i++){
if(L.data[i]!=x){ //即不为x的元素,应该保留
L.data[k]=L.data[i];
k++
}
L.length=k;
}
【算法思想】:
bool Merge(sqList A,sqList B,sqList C){
//判断C的长度是否符合
if(A.length+B.length>C.MaxSize)
return false;
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];
i++;
k++;
}
else{
C.data[k]=B.data[i];
j++;
k++;
}
}
//若A中还有剩余
while(i<A.length)
C.data[k++]=A.data[i++];
//若B中还有剩余
while(i<B.length)
C.data[k++]=B.data[j++];
C.length=k;
return true;
}
注意:空间复杂度O(1)即辅助空间顶多只能是常数个。
【方法】:
①常规方法
【算法思想】:
将前面一半的元素依次对应的和后面一半的元素交换。
void Reverse(sqList &L){
Elemtype temp; //用于交换的辅助变量
for(int i=0;i<L.length/2;i++){
temp=L.data[i];
//L.length代表顺序表长度,等于n+1
L.data[i]=L.data[L.length-i-1];
L.data[L.length-i-1]=temp;
}
}
//导入数组A
void Reverse(int *A,int low,int high){
if(low<high){
swap(A[low],A[high]);
Reverse(A,low+1,high-1);
}
}
void swap(int *x, int *y){
int temp;
temp = *x;
*x = *y;
*y = temp;
}
【算法思想】:
遍历顺序表,将s到t的之间值的个数 记为k,将不是s到t之间的数向前移动k个单位。
bool Del_st(sqList &L,ElemType s,ElemType t){
int k=0;
int i=0;
if(L.length==0 || s>=t){
return false;
}
for(i=0;i<L.length;i++){
if(L.data[i]>=s && L.data[i]<=t){
k++;
}
else{
L.data[i-k]=L.data[i];
}
}
L.length=L.length-k;
return true;
}
【算法思想】:
i | 所指的即需要保留的 |
---|---|
j | 工作指针:用于遍历顺序表 |
若i、j 所指元素值不相同,则代表该元素需要保留;若i、j相同,则代表该元素不保留
第一个元素一定不重复,因此工作指针j 初始可以指向第二个元素。
bool Del_same(sqList &L){
if(L.length==0)
return false;
//注意j是工作指针,用于遍历顺序表
for(i=0,j=1;j<L.length;j++){
//判断需要保留的元素
if(L.data[i]!= L.data[j]){
//注意保留时,i指针应该向后移动一位
L.data[++i]= L.data[j];
}
}
//i即保留的元素(数组长度是0开始,到i)
L.length=i+1;
return true;
}
【算法思想】:
使用3次逆置:
①(a1, a2, a3… am)逆置;
②(b1, b2,b3…bn)逆置;
③m+n整体逆置。
void Reverse(int A[],int from,int to){
int i,temp;
for(i=0;i<(to-from+1)/2;i++){
temp=A[from+i];
A[from+i]=A[to-i];
A[to-i]=temp;
}
}
void exchange(int A[],int m,int n){
Reverse(A,0,m-1);
Reverse(A,m,m+n-1);
Reverse(A,0,m+n-1);
}
设将n(n> 1)个整数存放到一维数组R中。设计一个在时间和空间两方面都尽可能高效的算法。将R中保存的序列循环左移p(0
1)给出算法的基本设计思想。
2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。
【算法思想】:
同第7题
void Reverse(int R[],int from,int to){
int i,temp;
for(i=0;i<(to-from+1)/2;i++){
temp=R[from+i];
R[from+i]=R[to-i];
R[to-i]=temp;
}
}
void exchange(int R[],int p,int n){
Reverse(R,0,p-1);
Reverse(R,p,n-1);
Reverse(R,0,n-1);
}
时间复杂度O(n),空间复杂度O(1)。
试编写带头节点的的单链表L中删除一个最小值节点的高效算法(假设最小值
节点是唯一的)
注:单链表算法时一定要注意【是否带有头节点】
【分析】:
①找:找最小值
利用两个指针
p | 遍历链表 |
---|---|
q | 标记最小值结点 |
②删
其前驱指向其后继;free§。
因此需要设置前驱指针
ppre | 指向p的前驱指针 |
---|---|
qpre | 指向q的前驱指针 |
【算法思想】:
LinkList Del_min(LinkList &L){
Lnode *ppre=L;
Lnode *p=ppre->next;
Lnode *qpre=ppre;
Lnode *q=p;
//找
while(p){
if(p->data<q.data){
q=p;
qpre=ppre;
}
else{
ppre=p;
p=p->next;
}
}
//删
qpre->next=q->next;
free(q);
}
【分析】:
特点——输入的数据次序 与 生产链表的结点数值次序 相反。
LinkList List_HeadInsert(LinkList &L){
Lnode *s;
int x;
//创建头结点,动态申请空间
LinkList L=(LinkList)malloc(sizeof(Lnode));
L->next=NULL;
scanf("%d",&x);
while(x!=-1){
s=(Lnode *)malloc(sizeof(Lnode));
s->data=x;
s->next=L->next; //将新结点插入表中-1
L->next=s; //将新结点插入表中-2
scanf("%d",&x); //继续输入新元素(直到输入-1结束)
}
return L;
}
使用尾插法建立单链表。
【分析】:
特点——输入的数据次序 与 生产链表的结点数值次序 相同。
LinkList List_TailInsert(LinkList &L){
Lnode *s;
int x;
//创建头结点,动态申请空间
LinkList L=(LinkList)malloc(sizeof(Lnode));
*r=L;
scanf("%d",&x);
while(x!=-1){
s=(Lnode *)malloc(sizeof(Lnode));
s->data=x;
r->next=s;
r=s; //r始终指向表尾
scanf("%d",&x); //继续输入新元素(直到输入-1结束)
}
r->next=NULL; //表尾置空
return L;
}
试编写算法将带头节点的单链表就地逆置,所谓的“就地”是指辅助空间的复杂
度为O (1)。
【分析】:
看到逆置,优先考虑到头插法;
方法一:头插法
需要两个指针:
r | 标记剩下链表的第一个节点(防止断链) |
---|---|
p |
LinkList Reverse_LinkList(LinkList &L){
Lnode *p=L->next;
Lnode *r;
L->next=NULL;
while(p){
r=p->next; // 保留后继,防止断链
p->next=L->next;
L->next=p;
p=r;
}
}
方法二:调整指针
需要三个指针:
pre | 标志p的前驱 |
---|---|
p | |
r | 标记p的后继 |
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;
return L;
}
设在一个带表头结点的单链表中所有元素的结点的数据值无序,
试编写一个函数,删除表中所有介于给定的两个值(作为函数的参数给出)之间的元素的元素(若存在)。
【算法思想】:
遍历链表,删除在最小值和最大值之间的元素。
void Del_LinkList(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;
}
}
}
给定两个单链表,编写算法找出两个单链表的公共结点。
【分析】:
(则两个单链表应该是Y型)
❗注意:两个单链表未说明为等长,因此应该思考其不等长(结果才具有普遍性)
①遍历法->暴力法
【算法思想】:
p | La的指针 |
---|---|
q | Lb的指针 |
Fun_Search(LinkList &La,LinkList &Lb){
LNode *p=La->next;
LNode *q=Lb->next;
while(p!=NULL){
while(q!=NULL){
if(p==q)
return p; //找到公共结点,并返回
q=q->next;
}
p=p->next;
q=Lb->next;
}
}
时间复杂度:O(La * Lb)
②“双指针问题”
【算法思想】:
将问题转换成 La、Lb如何同时到达表尾,再进行p==q的比较
LinkList Search_Common(LinkList &La,LinkList &Lb){
LNode *p;
LNode *q;
int LenA=length(La);
int LenB=length(Lb);
if((LenA-LenB)>0){
k=LenA-LenB; //求出两个链表只差
p=La->next;
q=Lb->next;
}else(){
k=LenB-LenA;
p=Lb->next;
q=La->next;
}
//始终让较长的移动k位
while(k--){
p=p->next; //让p先向后面移动k位,此时p、q剩余的表长相同
}
while(p!=NULL){
if(p==q){
return p; //找到公共结点,并返回
}else{ //若没找到公共结点,则同步向后移动(保证同时到达表尾)
p=p->next;
q=q->next;
}
}
return NULL; //没找到公共结点,返回空
}
时间复杂度:O(LenA + LenB)
将一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含
有原表中序号为奇数的元素,而B表中含有原表中序号为偶数的元素,且保持
其相对顺序不变。
LinkList Create(LinkList &A){
int i=0; //通过i判断奇偶性
LinkList B=(LinkList)malloc(sizeof(Lnode));
B->next=NULL;
LNode *ra,rb=B;
LNode *p=A->next;
A->next=NULL; //置A链表为空(这一步和上一步不可颠倒)
while(p!=NULL){
i++;
if(i%2==0){
rb->next=p;
rb=p; //若是偶数,插入B中
}else{
ra->next=p;
ra=p; //若是奇数,插入A中
}
p=p->next;
}
ra->next=NULL;
rb->next=NULL; //两链表末尾置空
return B; //A链表已有,因此只需要返回B链表
}
设C={a1, b1, a2, b2, …an, bn}为线性表,采用带头结点的hc单链表存放,设计一个就地算法,将其拆分为两个线性表,使得A={a1, a2, … an},B= {bn,… b2, b1}。
法①
【算法思想】:
遍历C链表,将第一个结点尾插在A中,第二个结点头插在B中…依次如此,直到将C拆分为A、B。
LinkList Create(LinkList &hc){
LNode *ra=hc; //尾插法的尾指针
LNode *p=hc->next; //遍历指针
LinkList A=(LinkList)malloc(sizeof(Lnode));
LinkList B=(LinkList)malloc(sizeof(Lnode));
A->next=NULL; //第一个结点初始化,置空
B->next=NULL;
A->next=NULL;
//交替尾插、头插、尾插、头插...
while(p!=NULL){ //遍历整个C链表
ra->next=p; //A-尾插
ra=p;
p=p->next;
if(p!=NULL){
r=p->next; //头插防断链
p->next=B->next; //B-头插
B->next=p;
p=r;
}
return A;
return B;
}
法②
注:也可以使用15题的方法,定义i,通过i%2==0判断奇偶性;若奇数则尾插至A,若偶数则尾插至B。
在一个递增有序的线性表中,有数值相同的元素存在。若存储方式为单链表,设计算法去掉数值相同的元素,使表中不再有重复的元素,例如(7, 10, 10,21,30, 42, 42,42, 51, 70)将变成(7,10, 21, 30, 42, 51, 70)。
【分析】:
由于递增有序,则值域相同元素一定是相邻的。
“双指针问题”
法①多设置前驱指针pre
【算法思想】:
设置两个指针pre、p;
若两指针的值相同,则删除p指针。
void Del_Commondata(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;
}
}
}
法②多设置后继指针q
void Del_Commondata(LinkList &L){
LNode *p=L->next;
LNode *q;
while(p->next!=NULL){
q=p->next;
if(p->data==q->data){
p->next=q->next;
free(q);
}else{
p=p->next;
}
}
}
假设有两个按元素值递增次序排列的线性表,均以单链表的形式存储。请编写算法将这两个单链表归并为一个按元素值递减次序排列的单链表,并要求利用原本两个单链表的结点存放归并后的单链表。
【算法思想】:
LinkList merge(LinkList &A,LinkList &B){
LNode *p=A->next;
LNode *q=B->next;
A->next=NULL;
LNode *r;
while(p!=NULL&&q!=NULL){
if(p->data<q->data){
r=p->next;
p->next=A->next;
A->next=p;
p=r;
}else{
r=q->next;
q->next=A->next;
A->next=q;
q=r;
}
while(q!=NULL){ //若B链表有剩余
r=q->next;
q->next=A->next;
A->next=q;
q=r;
}
while(p!=NULL){ //若A链表有剩余
r=p->next;
p->next=A->next;
A->next=p;
p=r;
}
}
}
设A和B是两个单链表(带头结点),其中元素递增有序。设计一个算法从A和B中的公共元素产生单链表C,要求不破坏A、B的结点。
LinkList Common(LinkList A,LinkList B){ //要求不破坏AB链表,因此可以不加取地址符
LNode *p=A->next;
LNode *q=B->next;
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; //两个元素均向后移动1位
q=q->next;
}
}
r->next=NULL; //C链表置空
}
※已知两个链表A和B分别表示两个集合,其元素递增有序排列。编制函数,求A和B的交集,并存放于A链表中。
【分析】:
【算法思想】:
(不符合要求的应删掉->边比较边释放空间)
LinkList Common_A(LinkList &A,LinkList &B){
LNode *p=A->next;
LNode *q=B->next;
LNode *r=A; //尾插留尾指针
A->next=NULL;
LNode *u;
while(p!==NULL&&q!=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{ //即找到了公共元素
r->next=p;
r=p; //保留p的同时,应该考虑释放q
p=p->next;
u=q; //释放q
q=q->next;
free(u);
}
}
while(p!=NULL){ //若A中有剩余,则将剩下全部释放
u=p;
p=p->next;
free(u);
}
while(q!=NULL){ //此处是讨论B中剩余,最好写上
u=q; //释放q
q=q->next;
free(u);
}
r->next=NULL; //C链表置空
free(B);
return A;
}
两个整数序列A= a1,a2,a3… .am和B=b1, b2, b3… bn已经存入两个单链表中,设计一个算法,判断序列B是否是序列A的连续子序列。
①暴力法->枚举
【算法思想】:
bool Compare_lianxu(LinkList A,LinkList B){
LNode *p=A->next;
LNode *q=B->next;
LNode *pre=p; //pre指针记录的是p节点的位置
while(p!=NULL && q!=NULL){
if(p->data != q->data){
pre=pre->next; //A从上次节点的后继节点 再次开始新一轮比较
p=pre;
q=B->next;
}else{
p=p->next;
q=q->next;
}
}
if(q=NULL)
return true;
else
return false;
}
②
设计一个算法用于判断带头结点的循环双链表是否对称。
【算法思想】:
int Compare_Duichen(DLinkList L){
DNode *p=L->next;
DNode *q=L->prior;
while(p!=q && q->next=p){ //也可以直接写为while(p!=q)
if(p->data==q->data){
p=p->next;
q=q->prior;
}else
return 0;
}
return 1;
}
有两个循环单链表,链表头指针分别为h1和h2,编写一个函数将链表h2链接到链表h1之后,要求链接后的链表仍保持循环链表的形式。
【算法思想】:
分别为两个循环单链表 设置表尾指针p、q;
LinkList Add(LinkList &h1,LinkList &h2){
LNode *p,*q;
//寻找h1的表尾p
p=h1;
while(p->next!=h1){
p=p->next;
}
//寻找h2的表尾q
q=h2;
while(q->next!=h2){
q=q->next;
}
//修改p、q指针指向
p->next=h2;
q->next=h1;
}
设有一个带头节点的的循环单链表,其结点值均为正整数。设计一个算法,反复找出单链表中结点值最小的结点并输出,然后将该结点从中删除,直到单链表为空为止,再删除表头结点。
【算法思想】:
反复找出当前的最小值节点,并删掉,直到链表为空,释放头结点L。
❗注意是 反复 寻找。
void Del_All(LinkList &L){
LNode *p,*pre,*minp,*minpre; //先只将指针定义
while(L->next!=L){ //保证“反复“查找最小值并删除
p=L->next; //注意每次开始循环一轮,都得进行指针的重置
pre=L;
minp=L->next;
minpre=L;
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);
}
}
设头指针为L的带有表头结点的非循环双向链表,其每个结点中除有pred(前驱指针)、data (数据)和next (后继指针)域外,还有一个访问频度域freq。
在链表被启用前,其值均初始化为零。每当在链表中进行一次Locate(L,x)运算时,令元素值为X的结点中freq域的值增加1,并使此链表中结点保持按访问频度非增(递减)的顺序排列,同时,最近访问的结点在频度相同的结点前面,以便使频繁访问的结点总是靠近表头。
试编写符合上述要求的Locate (L,x) 运算的算法,该运算为函数过程,返回找到结点的地址,类型为指针型。
【算法思想】:
LNode Locate(LinkList &L,ElemType x){
LNode *p=L->next;
LNode *q;
while(p&&p->data!=x){ //在双循环链表中查找x
p=p->next; //p不为空或其值不为x则向后移
}
if(p==NULL){
printf("不存在值为x的节点");
exit(0);
}else{ //即找到x。则开始后续操作(取下节点并插入)
p->freq++; //将其频率freq加1
q=p->pred; //从原节点的前驱节点开始
}
if(p->next!=NULL){ //判断p是否是最后一个节点
p->next->pred=p->pred; //取出p节点-2步
p->pred->next=p->next;
}else{
p->pred->next=NULL;
}
//q往前找,找到第一个比之大的。然后插入
while(q!=L && q->fred<=p->fred){ //查找p的插入位置
q=q->fred; //q往前找
}
p->next=q->next; //插入q-4步
q->next->pred=p;
p->pred=q;
q->next=p;
return p; //返回该节点
}
假设该链表只给出头指针List,在不改变链表的前提下,请设计一个尽可能高效的算法,
查找链表中的倒数第K个位置上的结点(k为正整数)。若查找成功,算法输出该结点data域的值,并返回1;否则,只返回0。要求:
1)描述算法的的基本设计思想。
2)描述算法的详细实现步骤。
3)根据设计思想和实现步骤,采用程序设计语言描述算法(使用C或者C++实现),关键之处给出简要注释。
❗注意:给了链域为link,则应该使用如p->next。
【分析】:
①找倒数第k个位置;
【算法思想】:
先将p指针移动到第k个位置;
然后p、q一起向后移动,直到p到达表尾,则q指向倒数第k个位置。
typedef struct LNode{
ElemType data;
struct LNode *Link;
}LNode;
int Search_k(LinkList List,int k){
LNode *p=List->link; p、q指向第一个节点
LNode *q=List->link;
int count=0;
//p先移动,当移动到第k个位置时,q再开始移动(之后p、q一起移动)
while(p!=NULL){
if(count<k)
count++;
else
q=q->link;
p=p->link;
}
if(count<k)
return 0;
else{
printf("%d",q->data);
return 1;
}
}
[2012统考真题]假定采用带头结点的单链表保存两个单词有相同的后缀时,可享受相同的后缀存储空间,例如,“loading"being”的存 储映像如下图所示。.
设str1和str2分别指向两个单词的单链表的头结点,链表结点结构为[data][link],
请设计一个时间上尽可能高效的算法,找出由str1和str2所指向两个链表共同后缀的起始位置(如图中字符i所在结点的位置p)。要求:
1)给出算法的基本设计思想
2)根据设计思想,采用C或者C++或语言描述算法,关键之处给出注释。
3)说明你算法的时间复杂度。
【同第14题】:给定两个单链表,编写算法找出两个单链表的公共节点。
【分析】:
单链表的结构是X型还是Y型
【方法】:
法一:暴力法
双重循环:p移动一位,而q移动一轮地比较p、q。
法二:“间隔一定,同步后移”
(遍历一次)
将问题转换为如何同时到达表尾。
【算法思想】:
//求单链表的长度
int length(SNode *head){
int len=0;
LNode *p=head->next;
while(p!=NULL){
len++;
p=p->next;
}
return len;
}
LinkList Search_Common(LinkList &str1,LinkList &str2){
LNode *p;
LNode *q;
m=length(str1);
n=length(str2);
for(p=str1->next;m>n;m--) //若m大,则p后移
p=p->next;
for(q=str2->next;m<n;n--) //若n大,则q后移
q=q->next;
while(p!=NULL && p!=q){
p=p->next;
q=q->next;
}
if(p=NULL)
return 0;
else
return p;
}
[2015统考真题]
用单链表保存m个整数,结点的结构为[data][link],且|data|≤n(n正整数)。
现要求设计一个时间复杂度尽可能高效的算法,对于链表中data的绝对值相等的结点,仅保留第一次出现的结点而删除其余绝对值相等的结点。
例如,若给定的单链表head如下:
要求:
1)给出算法的基本设计思想
2)使用C或者C++语言,给出单链表结点的数据类型定义。
3)根据设计思想,采用C或者C++语言描述算法,关键之处给出注释。
4)说明你所设计的算法的时间复杂度和空间复杂度。
【分析】:
①可能需要用空间换时间——开辟一个辅助数组。(重点是如何使用)
处理方法:标记数组——将链表中的值转换为数组中的次序
②第一次的值保留,其余重复均删掉。(重点是取绝对值)
处理方法:三目运算符
m=p->data>0?p->data : -p->data
(这样能保证m一定是一个正数)
【算法思想】:“以空间换时间”
typedef struct LNode{
int data;
struct LNode *Link;
}LNode;
void Del_Common(LinkList &head,int n){
LNode *p=head->link; //用于遍历
LNode *pre=head; //用于删除
C=(int *)malloc(sizeof(int)*(n+1)); //申请辅助数组空间
for(int i=0;i<n+1;i++) //数组C初始化为0
C.data[i]=0;
while(p!=NULL){
m=p->data>0?p->data:-p->data;
if(C.data[m]==0){ //若标记为0,则为第一次出现,需保留
C.data[m]=1; //将标记改为1
pre=p; //在链表中保留该节点
p=p->link;
}else{ //重复,删除
pre->link=p->link;
free(p);
p=pre->link;
}
}
free(C); //最后释放辅助数组
}
时间复杂度O(m)
空间复杂度O(n)
[2019统考真题]
设线性表L= (a1, a2,a3,…, an-2, an-1, an)采用带头结点的单链表保存,链表中的结点定义如下:
Typedef struct node{
int data;
struct node *next
} NODE;
请设计一个空间复杂度为O (1) 且时间上尽可能高效的算法,重新排列L中的各结点,得到线性表L1= (a1, an, a2, an-1,a3,an-2, … )。
要求:
1)给出算法的基本设计思想。
2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
3)说明你设计的算法的时间复杂度。
( 法一):
第一次遍历找出length长度;第二次遍历找到length/2即中间节点。
(法二):
“双指针法”——“让间隔保持倍数”——q指针向后移动1位而p指针向后移动2位,当p指针到达末尾时,则q指针指向中间节点。
②将后半段链表逆置;(头插法)
③将后半段 插入到 前半段的合适位置。
void Re_Link(LinkList &L){
LNode *p,*q,*r,*s;
p=L->next;
q=L->next;
//1.找中间节点
while(q->next!=NULL){
p=p->next; //p走1步
q=q->next;
if(q->next!=NULL) //再进行一次判断,再使q走第2步
q=q->next;
}
//2.从q开始后半段均需逆置
while(q!=NULL){
r=q->next; //头插防断链
q->next=p->next;
p->next=q;
q=r;
}
//3.合并
s=L->next;
q=p->next;
while(q!=NULL){
r=q->next; //防止断链
q->next=s->next; //这行和下行类似于头插法
s->next=q;
s=q->next; //更改s的位置
q=r //防止断链
}
}
【考点知识】:
顺序表/链表----头结点计数0;
顺序二叉树----头结点计数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;
else
j=j/2;
}
return [i];
}
}
二叉树的遍历算法:先序、中序、后序
①先序
void Preorder(Bi TreeT){
if(T!=NULL){
visit(T); //访问根结点
Preorder(T->Lchild); //访问左子树
Preorder(T->Rchild); //访问右子树
}
}
②中序
void InOrder(Bi TreeT){
if(T!=NULL){
InOrder(T->Lchild); //访问左子树
visit(T); //访问根结点
InOrder(T->Rchild); //访问右子树
}
}
③后序
void PostOrder(Bi TreeT){
if(T!=NULL){
PostOrder(T->Lchild); //访问左子树
PostOrder(T->Rchild); //访问右子树
visit(T); //访问根结点
}
}
【总结】
①visit(T)的位置;
②递归
void InOrder(BiTree T){
Initstack(s);
BiTree p=T;
while(p || IsEmpty(s)){
if(p){ //若左孩子不为空,则一直向左走
push(s,p);
p=p->Lchild;
}else{
pop(s,p); //栈顶元素出栈并访问
visit(p);
p=p->Rchild; //向右子树走
}
}
}
后序遍历的非递归算法。
【算法思想】:
void PostOrder2(BiTree T){
InitStack(s);
BiTree p=T;
BiTree r=NULL;
while(p || IsEmpty(s)){
if(p){
push(s,p);
p=p->Lchild;
}else
GetTop(s,p);
if(p->Rchild&&p->Rchild!=r){ r标记最近访问的节点
p=p->Rchild;
push(s,p);
p=p->Lchild;
}else{
pop(s,p); 若右子树为空或被访问过则出栈
r=p;
p=NULL;
}
}
}
二叉树的层次遍历算法。
【算法思想】:
使用队列!
void LevelOrder(BiTree T){
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);
}
}
试给出二叉树的自下到上、从右往左的层次遍历算法。
【算法思想】:“层次遍历的改造”
void LevelOrder(BiTree bt){
InitQueue(Q); //初始化队列
InitQueue(S); //初始化栈
BiTree p;
if(bt!=NULL){
EnQueue(Q,T); //将根节点入队
}
while(!IsEmpty(Q)){
DeQueue(Q,p);
push(s,p); //元素出队以后,入栈
if(p->Lchild!=NULL) //若有左子树,则将左子树入队
EnQueue(Q,p->Lchild);
if(p->Rchild!=NULL) //若有右子树,则将右子树入队
EnQueue(Q,p->Rchild);
}
while(!IsEmpty(S)){
pop(s,p); //将所有入栈的元素出栈
visit(p->data);
}
}
假设二叉树采用二叉链表存储结构,设计一个算法求二叉树的高度(递归和非递归)。
【分析】: