1、线性表是 一个有限序列,可以为空
2、链表中最常用的操作是在最后一个元素之后插入一个元素和删除最后一个元素,则采用单循环链表存储方式最节省运算时间
3、若某线性表中最常用的操作实在最后一个元素之后插入一个元素和删除第一个元素,则采用仅有尾结点的单循环链表存储方式最节省时间
4、向栈顶指针为top的链栈中插入一个p所指结点时,其操作步骤为p->next=top;top=p;
5、对于栈操作数据的原则是后进先出
6、用不带头结点的单链表存储队列时,在进行删除运算时头、尾指针都要修改
7、用单链表表示链式队列时,队尾最好设在链表的链表尾位置
1、顺序存储的线性表可以按序号随机存取(√)
顺序表中每一个数据元素的存储位置都和线性表的起始位置相差一个和数据元素在线性表中的为序成正比的常熟。由此,只要确定了存储线性表的起始位置,顺序表中任一数据元素都可以随机存取。
2、线性表中的元素可以是各种各样的,但同一线性表中的数据元素具有相同的特性,因此属于同一数据对象。(√)
书上定义
3、在线性表的顺序存储结构中,逻辑上相邻的元素在物理位置上并不一定相邻。(×)
在线性表的顺序存储结构中,逻辑上相邻的两个元素在存储位置上一定是相邻的。
4、线性表的链式存储结构是用一组任意的存储单元来存储线性表中数据元素的。(√)
书上定义
5、在单链表中,要取得某个元素,只要知道该元素的指针即可,因此,单链表是随机存取的存储结构。(×)
单链表不是一种随机存取的存储结构
6、静态链表既有顺序存储的优点,又有动态链表的优点。所以它存取表中第i个元素的时间与i无关。(×)
读取某个元素时,是和i有关的
7、循环队列通常用指针来实现队列的头尾相接。(×)
通常用数组
8、每种数据结构都是一种数据类型。(×)
数据结构是一种数据类型
9、使用一个数组能够实现多个队列。(√)
1、循环队列的优点是什么?如何判断它的空和满/
循环队列的优点是能够克服“假溢满”现象。
设有循环队列sq,队满的判别条件是:
(sq->rear+1)%maxsizesq->front;或sq->nummaxsize
队空的判别条件是:
sq->rear==sq->front
2、栈和队列数据结构各有什么特点,什么情况下用到栈,什么情况下用到队列?
栈和队列都是操作受限的线性表,栈的运算规则是“后进先出”,队列的运算规则是“先进先出”。栈的应用如数制转换、递归算法的实现等,;队列的应用如树的层次遍历等。
3、请简述一种判定给定的链表是以NULL结尾,还是形成一个环的方法?
法一:从表头开始遍历,针对每个结点均检查是否存在它之后的某个节点的后继指针指向该节点,如果存在则说明该链表存在环。如果一直遍历到表尾结点都未发现这种结点,则说明该链表不存在环。很显然这种算法是一种效率很差的算法。
法二:使用散列(哈希表):从表头开始逐个遍历链表中的每个结点,并检查其是否已经存在散列表中。如果存在则说明已经访问过该结点了,也就是存在环;如果一直到表尾都没有出现已经访问过的结点,则说明该链表不存在环。
法三:Floyd环判定算法:使用两个在链表中具有不同移动速度的指针(如p每次移动两个结点,q每次移动一个结点),两个指针同时从表头开始移动,如果在某一时刻他们相遇了,则表明该链表存在环。原因很简单:快速移动指针和慢速移动指针将会指向同一位置的唯一可能性就是整个或者部分链表是一个环。
4、设有编号为1、2、3、4的四辆车,顺序进入一个栈式结构的站台,试写出这四辆车开出车站的所有可能的顺序(每辆车可能入站,可能不入站,时间也可能不等)。
1234,1243,1324,1342,1432,213,2143,2314,2341,2431,3214,3241,2421,4321
5、如何使用两个队列高效地实现一个栈,并分析该栈基本操作的时间复杂度。
利用两个栈s1和s2模拟一个队列。当需要向队列中插入一个元素时,用s1来存放一输入的元素,即通过向栈s1执行入栈操作来实现;当需要从队列中删除元素时,则将s1中的元素全部送到s2中,再从s2中删除栈顶元素,最后再将s2中元素全部都送到s1中。
从复杂度来说,队列的pop操作最坏情况是将s1栈的数据都压入s2栈,再pops2栈的数据,最差是o(n),实际多数情况是o(1)
1、设线性表存放在向量A[arrisize]的前elenum个分量中,且递增有序。试写一算法,将x插入到线性表的合适位置上,以保持线性表的有序性,并且分析算法的时间复杂度。
注意,首先判断是否还有存储空间,有的话,从高下标端开始一边比较,一边移位,插入x,最后修改表的长度。
int insert(datatype A[],int *elenum,datatype x)//设elenum为表的最大下标
{
if(*elenum==arrize-1) return 0;//表满,无法插入
else{
i=*elenum;
while(i>=0&&A[i]>x)
{
A[i+1]=A[i];
i--;
}
A[i+1]=x;//找到的位置是插入的下一位
(*elenum)++;
return 1;//插入成功
}
}
可知时间复杂度为O(n)
2、已知一顺序表A,其元素值非递减有序排列,编写一个算法删除顺序表中多余的值相同的元素。
对顺序表A ,从第一个元素开始,查找其后与之相同的所有元素,将他们删除;再对后面的元素做相同处理。
void delete(Seqlist *A)
{
i=0;
while(i<A->last)//将第i个元素以后与其值相同的元素删除
{
k=i++;
while(k<=A->last&&A->data[i]==A->data[k])
k++;//使k指向第一个与A[i]不同的元素
n=k-i-1;//n表示要删除元素的个数
for(j=k;j<=A->last;j++)
A->data[j-n]=A->data[j];//删除多余元素
A->last=A->last-n;
i++;
}
}
3、写一个算法,从一个给定的顺序表A中删除值再x~y(x≤y)之间的所有元素,要求以较高的效率来实现。
对于顺序表A,从前往后依次判断当前元素A->data[i]是否介于x和y之间,若是,并不立即删除,而是用n记录删除时应该前移元素的位移量;若不是,则将A->data[i]向前移动n位。n用来记录当前已删除元素的个数。
void delete(Seqlist *A,int x,int y)
{
i=0;
n=0;
while(i<A->last)
{
if(A->data[i]>=x&&A->data[i]<=y) n++;
else A->data[i-n]=A->data[i];
i++;
}
A->last=n;
}
4、线性表中有n个元素,每个元素是一个字符,现存于向量R[n]中,试写一算法,使R中的字符按字母字符、数字字符和其他字符的顺序排列。要求利用原来的存储空间,元素移动次数最小。
对线性表进行两次扫描,第一次将所有的字母放在前面,第二次将所有的数字放在字母之后,其他字符之前。
int fch(char c)//判断c是否是字母
{
if(c>='a'&&c<='z'||c>='A'&&c<='Z') return 1;
else return 0;
}
int fnum(char c)//判断c是否是数字
{
if(c>='0'&&c<='9') return 1;
else return 0;
}
void process(char R[n])
{
low=0;
high=n-1;
while(low<high)//将字母放在前面
{
while(low<high&&fch(R[low])) low++;
while(low<high&&!fch(R[high])) high--;
if(low<high)
{
k=R[low];
R[low]=R[high];
R[high]=k;
}
}
low=low+1;
high=n-1;
while(low<high)//将数字放在字母后面,其他字符前面
{
while(low<high&&fnum(R[low])) low++;
while(low<high&&!fnum(R[high])) high--;
if(low<high)
{
k=R[low];
R[low]=R[high];
R[high]=k;
}
}
}
5、线性表用顺序存储,设计一个算法,用尽可能少的辅助存储空间将顺序表中前m个元素和后n个元素进行整体互换。即将线性表:
(a1,a2,a3,…,am,b1,…bn)变成
(b1,b2,…bn,a1,…,am)
比较m和n的大小,若m<n,则将表中元素依次前移m次;否则,将表中元素依次后移n次。
void process(Seqlist *L,int m,int n)
{
if(m<=n)
for(i=1;i<=m;i++)
{
x=L->data[0];
for(k=1;k<=L->last;k++)
L->data[k-1]=L->data[k];
L->data[L->last]=x;
}
else
for(i=1;i<=n;i++)
{
x=L->data[L->last];
for(k=L->last-1;k>=0;k--)
L->data[k+1]=L->data[k];
L->data[0]=x;
}
}
6、已知带头结点的单链表L中的节点是按照整数值递增排列的,试写一算法,将值为x的节点插入到表L中,是的L仍然递增有序,并且分析算法的时间复杂度。
LinkList insert(LinkList L,int x)
{
p=L;
while(p->next&&x>p->next->data)
p=p->next;//寻找插入位置
s=(LNode*)malloc(sizeof(LNode));//申请节点空间
s->data=x;
s->next=p->next;
p->next=s;
return L;
}
7、假设有两个已排序(递增)的单链表A和B,编写算法将他们合并成一个链表C而不改变其排序性。
LinkList Combine(LinkList A,LinkList B)
{
C=A;
rc=C;
pa=A->next;//pa指向表A的第一个节点
pb=B->next;//pb指向表B的第一个节点
free(B); //释放B的头结点
while(pa&&pb)//将pa、pb所指向节点中,值较小的一个插入到链表C的表尾
if(pa->Data<pb->data)
{
rc->next=pa;
rc=pa;
pa=pa->next;
}
else
{
rc->next=pb;
rc=pb;
pb=pb->next;
}
if(pa) rc->next=pa;
else rc->next=pb;//将链表A或B中剩余的部分链接到链表C的表尾
return C;
}
8、假设长度大于1的循环单链表中,既无头结点也无头指针,p为指向该链表中某一结点的指针,编写算法删除该节点的前驱结点.
利用循环单链表的特点,通过s指针可循环找到其前驱节点p及p的前驱结点q,然后可以删除节点*p;
void delepre(LNode *s)
{
LNode *p,*q;
p=s;
while(p->next!=s)
{
q=p;
p=p->next;
}
q->next=s;
free(p);
}
9、已知两个单链表A和B分别表示两个集合,其元素递增排列,编写算法求出A和B的交集C,要求C同样以元素递增的单链表形式存储。
交集指的是两个单链表的元素值相同的节点的集合,为了操作方便,先让单链表C带一个头结点,最后将其删除掉。算法中指针p用来指向A中的当前节点,指针q用来指向B中的当前节点,将其值进行比较,两者相等时,属于交集中的一个元素,两者不等时,将较小者跳过,继续后面的比较。
LinkList Intersect(LinkList A,LinkList B)
{
LinkList C;
C=(LNode*)malloc(sizeof(LNode));
C->next=NULL;
r=C;
p=A;
q=B;
while(p&&q)
if(q->data<p->data) 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;
C=C->next;
return C;
}
10、写一个算法,只扫描一次单链表,就能找到链表中的倒数第n个节点。
不管是正数n个还是倒数n个,其实都是距离-标尺问题。标尺是一段距离可以用线段的两个端点来衡量,我们能够判断倒数第一个节点,因为它的next==NULL。如果我们用两个指针,并保持他们的距离为n,那么当这个线段的右端指向末尾节点时,左端节点就指向倒数第n个节点。
建立两个指针,第一个先走n步,然后第2个指针也开始走,两个指针步伐(前进速度)一致。当第一个节点走到链表末尾时,第二个节点的位置就是我们需要的倒数第n个节点的值。
typedef int Status;//Status是函数的类型,其值是函数结果状态代码,如OK等。
typedef int ElemType;//ElemType类型根据实际情况而定,这里假设为int
typedef struct Node
{
ElemType data;
structNode *next;
}Node;
typedef struct Node*LinkList;//定义LinkList
Status visit(ElemType c)
{
printf("%d",c);
return ok;
}
//查找倒数第n个元素
Status GetNthNodeFromBack(LinkList L,int n,ElemType *e)
{
int i=0;
LinkList firstNode=L;
while(i<n&&firstNode->next!=NULL)
{
//整数N个节点,firstNode指向正的第N个节点
i++;
firstNode=firstNode->next;
printf("%d\n",i);
}
if(firstNode->next==NULL&&i<n-1)
{
//当节点数量少于N个时,返回NULL
printf("超出链表长度\n");
return ERROR;
}
LinkList secNode=L;
while(firstNode!=NULL)
{
//查找倒数第N个元素
secNode=secNode->next;
firstNode=firstNode->next;
}
*e=secNode->data;
return OK;
}
11、设有一个双向链表,每个节点中除有prior、data和next域外,还有一个访问贫富freq域,在链表被启用之前,该域的值初始化为零,每当在链表进行一次Locata(L,X)运算之后,令值为x的节点中的freq域增1,并调整报表中节点的次序,使其按照访问频度的非递增序列排列,以便使频繁访问的节点总是靠近表头。是写一个满足上述要求的Locata(L,x)算法。
在定位操作的同时,需要调整链表中节点的次序:每次进行定位操作之后,要查看所查找节点的freq域,将其同前面节点的freq域进行比较,同时进行节点次序的调整。
typedef struct dnode
{
datatype data;
int freq;
struct DLnode *prior,*next;
} DLnode,*DlinkList;
DlinkList Locate(DlinkList L,datatype x)
{
p=L->next;
while(p&&p->data!=x) p=p->next;//查找值为x的节点,使p指向它
if(!p) return NULL;//若查找失败,返回空指针
p->freq++;//修改p的freq域
while(p->prior!=L&&p->prior->freq<p->freq)//调整节点的次序
{
k=p->prior->data;
p->prior->data=p->data;
p->data=k;
k=p->prior->freq;
p->prior->freq=p->freq;
p->freq=k;
p=p->prior;
}
return p;//返回找到的节点的地址
}
12、称正读和反读都相同的字符序列为“回文”,例如,“abcddcba”、“qwerewq”是回文,“ashgash”不是回文。试着写一个算法判断读入的一个以“@”为结束符的字符序列是否为回文。
使用栈。将字符串的前一半入栈,再依次出栈,与后一半进行比较,若有不等则不是回文,若依次相等则是回文。
int Huiwen(char a[],int n)
{
SeqStack *S;
char *str;
int i=0,j=1;
Init_Stack(S);//初始化栈s
while(i<n/2)//字符串的前一半入栈
{
Push_Stack(S,a[i]);
i++;
}
if(n%2!=0) i++;//若n为奇数,i加一,越过中间的一个数。
while(i<n&&j==1)
if(a[i]==Top_Stack(S))
{
Pop_Stack(S,str);
i++;
}
else
{
j=0;
break;
}
return j;//返回值j=0,则不是回文;若j=1,则是回文。
}
13、设以数组se[m]存放循环队列的元素,同时设变量rear和front分别作为队头队尾指针,且队头指针指向队头的前一个位置,写出这样设计的循环队列入队和出队方法。
//入队方法
int EnQueue(datatype se[m],int rear,int front,datatype e)
{
if((rear+1)%m==front) return -1;//队满,返回-1
rear=(rear+1)%m;
se[rear]=e;
return 1;
}
//出队方法
int DeQueue(datatype se[m],int rear,int front,datatype *e)
{
if(rear==front) return -1;//若队空,返回-1
front=(front+1)%m;
*e=se[front];
return 1;
}
14、假设以数组se[m]存放循环队列的元素,同时设变量rear和num分别作为队尾指针和队中元素个数记录,试给出判别次循环队列的队满条件,并写出相应入队和出队算法。
//入队方法
int EnQueue(datatype se[m],int rear,int num,datatype e)
{
if(num==m) return -1;
rear=(rear+1)%m;
se[rear]=e;
num++;
return 1;
}
//出队方法
int DeQueue(datatype se[m],int rear,int num,datatype *e)
{
if(num==0) return -1;
*e=se[(rear-num+1)%m];
num--;
return 1;
}
15、假设以带头结点的循环链表表示一个队列,并且只设一个队尾指针指向尾元素节点(注意不设头指针),试写出相应的置空队、入队、出队的算法。
带头结点的循环链表表示的队列如下,仅有队尾指针rear,但可通过rear->next找到头结点,再通过头结点找到队头,即rear->next->next。
//置空队
LinkList SetNull()
{
LinkList rear;
rear=(LNode*)malloc(sizeof(LNode));
rear->next=rear;
return rear;
}
//入队
LinkList EnQueue(LinkList rear,datatype x)
{
LinkList p;
p=malloc(sizeof(LinkList));
p->data=x;
p->next=rear->next;//将p插入到rear->next之后
rear->next=p;
rear=p;
return rear;//返回新的队尾指针
}
//出队
LinkList DeQueue(LinkList rear,datatype *x)
{
if(rear->next==rear)//若队空,则输出队空信息
cout<<"empty";
else{
q=rear->next;
p=q->next;
} //否则q指向头节点,p指向队头
if(p==rear) rear=q;//若队中只有一个元素,则将rear指向头结点
q->next=p->next;//将p所指结点出队
*x=p->data;
free(p);
return rear;//返回队尾指针
}
16、设计一个算法判别一个算术表达式的原括号是否正确配对。
如果遇到左括号就压入栈中,如果遇到右括号则与栈顶元素比较,如果是与其配对的括号,则弹出栈顶元素;否则,括号就不配对。
int process()
{
Init_Stack(S);
cin>>c;//读入字符c
while(c!='@')//@为结束标志符
{
if(c=='(') Push_Stack(S,c);//遇到左括号,入栈
if(c==')')
{
if(!empty_stack(S)) Pop_Stack(S,a);
else return -1;
} //当字符是右括号时,判断栈的状态;若栈空,则返回-1,否则出栈
cin>>c;
}
if(Empty_stack(S)) return 1;
else return -1;//若栈空,说明表达式中括号配对,返回1;否则,返回-1.
}
17、编写算法,借助于栈将一个单链表置逆。
利用栈后进先出的特点,将单链表中的节点从链表头开始依次压栈,然后再依次出栈,采用尾插法重新生成单链表。
void reverse(LinkList A)
{
p=A->next;
r=A;
A->next=NULL;
Init_Stack(S);//初始化栈
while(p)//将链表中的节点依次入栈
{
Push_Stack(S,p);
p=p->next;
}
while(!Empty_Stack(S)) //将栈中元素依次出栈
{
Pop_Stack(S,q);
r->next=q;
r=q;
}
r->next=NULL;
}
18、两个栈共享向量空间v[m],他们的栈底分别设在向量的两端,每个元素占一个分量,试写出两个栈公用的栈操作算法:push(S,i,x),pop(S,i)和top(S,i)
,其中s表示栈,i=0和1,用以指示栈号。
双向栈是两个栈共享一个向量空间,栈0的底下标为0,栈1的地下标为m-1,初始时栈0的顶为-1,栈1的顶为m;当栈0有元素进栈时,让栈0的栈顶指针为1;退栈时减1.当栈1有元素进栈时,让栈1的栈顶指针-1,退栈时+1
#define m 100//设定义双向栈的向量空间的大小
typedef int datatype//定义栈元素类型
typedef struct
{
datatype v[m];//定义栈空间
int top0,top1;//0为0栈栈顶位置,1为1栈栈顶位置
}
//入栈
void push(dstack *S,int i,datatype x)
{
if(i!=0||i!=1) cout<<"error!";
else if(i==1)
{
if(S->top1-1==S->top0)
cout<<"overflow!";//栈已满
else{
S->top1--;
S->v[S->top1]=x;
}
}
else{
if(S->top0+1==S->top1) cout<<"overflow!";//栈已满
else{
S->top0++;
S->v[S->top0]=x;
}
}
}
//出栈
datatype pop(dstack *S,int i)
{
if(i!=0||i!=1) cout<<"underfloe!";
else if(i==1)
{
if(S->top1==m) cout<<"underflow!";//栈1空
else
{
S->top1++;
return(S->v[S->top1-1]);
}
}
else{
if(S->top0==-1) cout<<"underflow!";//栈0空
else{
S->top0--;
return (S->v[S->top0+1]);
}
}
}
//读栈顶元素
datatype top(dstack *S,int i)
{
if(i!=0||i!=1) cout<<"error!";
else if(i==1)
if(S->top1==m) cout<<"underflow!";//栈1空
else return (S->v[S->top1]);
else if(i==0)
if(S->top==-1) cout<<"underdflow!";//栈0空
else return(S->v[S->top0]);
}
~~19、请设计一个算法在一个数组中实现三个栈。
使用三个指针分别来指向三个栈的栈顶。因为存在三个栈,所以这就导致一个栈(比如栈a)的栈顶指针并不是指向下一个元素入栈的位置。所以我们需要一个指针指向当下一个入栈的位置(不论是入哪个栈),对于出栈,我们需要出栈后栈顶指针需要指向哪个位置,所以对于每个元素我们需要有一个域来记录他上一个入栈元素的位置。
我们还需要一个标志符来标志说明是否栈空。
数组的大小可以用来标志栈满。
但是需要注意的是,一个栈空了,其他栈不一定空。但是一个栈满了,就等于其他栈也满了(因为数组已经被填满,没有位置再放别的元素了)。~~
~~20、给定一个整数栈,如何检查栈中每对相邻数字是否连续。每对数字的值可以是递增或递减的,如果栈中元素的个数是奇数,那么组队时忽略栈顶元素。例如,假设栈中元素为[4,5,-1,-3,11,10,5,6,20],那么算法应该输出真,因为每对二元组(4,5)、(-2,-3)、(11,10)和(5、6)都是连续的数字。
public static boolean checkStackPairwiseOrder(LinkedListStack<Integer>stack)
{
LinkListQueue<Integer>queue=new LinkListQueue<Integer>();
boolean pairwiseOrdered=true;
//出栈——》入队——》队列元素(队首-》队尾)20,6,5,10,11,-3,-2,5,4
while(!stack.isEmpty()){
queue.enQueue(stack.pop());
}
//出队——》入栈——》栈中元素(栈底-》栈顶)20,6,5,10,11,-3,-2,5,4
while(!queue.isEmpty())
{
stack.push(queue.deQueue());
}
//出栈——》入队——》队列元素(队首-》队尾)4,5,-2,-3,11,10,5,6,20
while(!stack.isEmpty()){
int m=stack.pop();
queue.enQueue(m);
if(!stackisEmpty()){
int n=stack.pop();
queue.enQueue(n);
if(Math.abs(m-n)!=1){
pairwiseOrdered=false;
}
}
}
//出队——》入栈——》栈中元素(栈底-》栈顶)4,5,-2,-3,11,10,5,6,20
while(!queue.isEmpty()){
stack.push(queue.deQueue());
}
return pairwiseOrdered;
}
```~~