线性表的应用
标签: 数据结构
学习人数: 26.1k


高清播放
赞赏支持

顺序表上基本操作的实现

线性表在采用不同的存储结构时,它的描述方法是不一样的,那么它的基本操作实现方法也截然不同。下面来看线性表在顺序存储下,也就是顺序表的每一个基本操作的具体实现方法以及编写方法。

 

插入操作

还是上一个例子,一群朋友去吃火锅。此时有一个女生过来了,她叫小红。小红是小绿的女朋友,当然想和小绿坐在一起,但是小绿旁边是没有空位置的。所以小绿麻烦他旁边的朋友小黄和小黑依次移动了一个位置。这样就空出了一个位置,小红就可以过来坐下了。这样就完成了一个类似顺序表插入的过程。

代码实现

知道了顺序表插入的大致过程,来看它用程序语言是如何编写的。首先画出了一个顺序表

其中数据元素是连续存放的。接着标出了存放该顺序表数据元素的数组下标。注意一下,数组下标是从 0 开始的,而顺序表的元素标号是从 1 开始的。接着用 C++ 写出了该插入操作的函数体:

bool ListInsert(SqList &L, int i, ElemType e) {  
    if(i<1 || i>L.length+1)  
        return false;  
    if(L.length >= MaxSize)  
        return false;  
    for(int j=L.length; j>=i; j--)  
        L.data[j] = L.data[j-1];  
    L.data[i-1] = e;  
    L.length++;  
    return true;  
}  

它的返回值类型是一个布尔类型,表示如果插入成功会返回一个 True ,插入失败会返回一个 False。它有三个参数,分别是一个引用类型的顺序表 L,这里为什么用引用类型?因为在函数体内部进行操作,其实是作用一个局部变量上的,不会真正作用到这个参数上,如果不用引用类型,是不能把插入操作真正作用到传入的顺序表上的。接着是一个整型变量 i,它表示的是插入的位置,要注意在采用插入操作时,往往是前插法,这是一个默认规定。还有一点要注意的就是,i 对应的是顺序表的表号而非数组的下标。最后一个参数是所插入的数据元素 e。

 

首先第一个条件 i<1 || i>L.length+1 ,顺序表的元素标号是从 1 开始的,i 表示的是插入的位置,如果插入的位置是 0 或者更小,又或者比 L.length+1 还大,都是不合法的。第二个条件判断的是插入的数组是否有足够空间去插入,因为数组不可以增加容量,一旦满了就没用新的空间去提供插入了。循环语句中申请了变量 j 初始化为顺序表的长度,j 进行减一操作,一直到 j=i 的时候中止循环。循环体中 L.data[j] = L.data[j-1] 的意思就是把每一个数据元素向后移了一位,一直移动到 ai 移动到原先 ai+1 的位置。 执行完了所有的循环,空出了一个位置用于插入,L.data[i-1] = e 就是把要插入的元素放到该位置。注意,在 i 的位置,元素对应的下标是 i-1。最后将顺序表的长度加一。

 

 

时间复杂度

再来看一下这个程序的时间复杂度,当循环次数最少时,它就是最好的时间复杂度,什么时候循环次数最少呢?发现当在顺序表的尾部,也就是 L.length+1 的时候...

登录查看完整内容


课后作业

课后习题

 

【2019年真题】设线性表L=(a1,a2,a3…,an-2,an-1,an)采用带头结点的单链表保存,链表中结点定义如下:

typedef struct node {  
    int data;  
    struct node* next;  
} NODE;  

请设计一个空间复杂度为O(1)且时间上尽可能高效的算法,重新排列L中的各结点,得到线性表L'=(a1,an,a2,an-1,a3,an-2…)。
要求:
(1) 给出算法的基本设计思想
(2) 根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
(3) 说明你所设计的算法的时间复杂度。

参考答案:
(1)算法的基本设计思想:
算法分3步完成。第1步,采用两个指针交替前行,找到单链表的中间结点;第2步,将单链表的后半段结点原地逆置;第3步,从单链表前后两段中依次各取一个结点,按要求重排。
(2)算法实现:

void change_list(NODE *h) 
{
    NODE *p, *q, *r, *s;
    p = q = h;
    while (q->next != NULL) //寻找中间结点
    {
        p = p->next;    // p走一步
        q = q->next;
        if (q->next != NULL) q = q->next; //q走两步
    }
    q = p->next;    //p所值结点为中间结点,q为后半段链表的首结点
    p->next = NULL;
    while (q != NULL)//将链表后半段逆置
    {
        r = q->next;
        p->next = q;
        q = r;
    }
    s = h->next;    //s指向前半段的第一个数据结点,即插入点
    q = p->next;    //q指向后半段的第一个数据结点
    p->next = NULL;
    while (q != NULL)//将链表后半段的结点插入到指定位置
    {
        r = q->next;   //r指向后半段的下一个结点
        q->next = s->next; //将q所指结点插入到s所指结点之后
        s->next = q;
        s = q->next;    //s指向前半段的下一个插入点
        q = r;
    }
}

(3)算法的时间复杂度:
参考答案的时间复杂度为O(n)。


【2012年真题】假定采用带头结点的单链表保存单词,当两个单词有相同的后缀时,则可共享相同的后缀存储空间,例如,“loading”和“being”的存储映像如下图所示。 

设 str1 和 str2 分别指向两个单词所在单链表的头结点,链表结点结构为,请设计一个时间上尽可能高效的算法,找出由 str1 和 str2 所指向两个链表共同后缀的起始位置(如图中字符 i 所在结点的位置 p)。要求: 
1)给出算法的基本设计思想。 
2)根据设计思想,采用 C 或 C++或 JAVA 语音描述算法,关键之处给出注释。 
3)说明你所设计算法的时间复杂度。

参考答案:
1)顺序遍历两个链表到尾结点时,并不能保证两个链表同时到达尾结点。这是因为两个链表的长度不同。假设一个链表比另一个链表长 k 个结点,我们先在长链表上遍历 k 个结点,之后同步遍历两个链表,这样就能够保证它们同时到达最后一个结点。由于两个链表从第一个公共结点到链表的尾结点都是重合的,所以它们肯定同时到达第一个公共结点。算法的基本设计思想: 
①分别求出 str1 和 str2 所指的两个链表的长度 m 和 n; 
②将两个链表以表尾对齐:令指针 p、q 分别指向 str1 和 str2 的头结点,若 m>=n,则使 p 指向链表中的第 m-n+1 个结点;若 m<n,则使 q 指向链表中的第 n-m+1 个结点,即使指针 p 和 q 所指的结点到表尾的长度相等。 
③反复将指针 p 和 q 同步向后移动,并判断它们是否指向同一结点。若 p 和 q 指向同一结点,则该点即为所求的共同后缀的起始位置。 
2)算法的 C 语言代码描述:

LinkNode *Find_1st_Common(LinkList str1,LinkList str2){  
    int len1=Length(str1),len2=Length(str2);  
    LinkNode *p,*q;  
    for(p=str1;len1>len2;len1--) //使 p 指向的链表与 q 指向的链表等长  
        p=p->next;  
    for(q=str2;len1<len2;len2--) //使 q 指向的链表与 p 指向的链表等长  
        q=q->next;  
    while(p->next!=NULL&&p->next!=q->next){//查找共同后缀起始点  
        p=p->next; //两个指针同步向后移动  
        q=q->next;  
    }  
    return p->next; //返回共同后缀的起始点  
}  

3)时间复杂度为:O(len1+len2)或 O(max(len1,len2)),其中 len1、len2 分别为两个链表的长度。 


【2010年真题】设将 n(n>1)个整数存放到一维数组 R 中。试设计一个在时间和空间两方面都尽可能高效的算法。将 R 中保存的序列循环左移 p(0<p<n)个位置,即将 R 中的数据由(X0, X1…Xn-1)变换为(Xp,Xp+1 …Xn-1, X0, X1…Xp-1)。要求: 
⑴ 给出算法的基本设计思想。 
⑵ 根据设计思想,采用 C 或 C++或 JAVA 语言描述算法,关键之处给出注释。 
⑶ 说明你所设计算法的时间复杂度和空间复杂度。 

参考答案:
(1)算法的基本设计思想: 
可以将这个问题看做是把数组 ab 转换成数组 ba(a 代表数组的前 p 个元素,b 代表数组中余下的 n-p 个元素),先将 a 逆置得到 a-1b,再将 b 逆置得到 a-1b-1,最后将整个 a-1b-1 逆置得到(a-1b-1)-1 =ba。设 Reverse 函数执行将数组元素逆置的操作,对 abcdefgh 向左循环移动 3(p=3)个位置的过程如下: 
Reverse(0,p-1)得到 cbadefgh; 
Reverse(p,n-1)得到 cbahgfed; 
Reverse(0,n-1)得到 defghabc; 
注:Reverse 中,两个参数分别表示数组中待转换元素的始末位置。 
(2)使用 C 语言描述算法如下: 

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;   
    }   
}//Reverse  
  
void Converse(int R[],int n,int p) {   
    Reverse(R,0,p-1);   
    Reverse(R,p,n-1);   
    Reverse(R,0,n-1);   
}  

(3)上述算法中三个 Reverse 函数的时间复杂度分别为O(p/2) 、O((n-p)/2)和O(n/2),故所设计的算法的时间复杂度为O(n),空间复杂度为O(1)。
另解,借助辅助数组来实现: 
算法思想:创建大小为 p 的辅助数组 S,将 R 中前 p 个整数依次暂存在 S 中,同时将 R 中后 n-p 个整数左移,然后将 S 中暂存的 p 个数依次放回到 R 中的后续单元。 
时间复杂度为O(n),空间复杂度为O(p)。


【2009年真题】已知一个带有表头结点的单链表,结点结构为: 

假设该链表只给出了头指针 list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的 data 域的值,并返回 1;否则,只返回 0。要求: 
⑴ 描述算法的基本设计思想; 
⑵ 描述算法的详细实现步骤; 
⑶ 根据设计思想和实现步骤,采用程序设计语言描述算法(使用 C、C++或 Java 语言实现),关键之处请给出简要注释。 

参考答案:
(1)算法的基本设计思想: 
问题的关键是设计一个尽可能高效的算法,通过链表的一趟遍历,找到倒数第 k 个结点的位置。算法的基本设计思想是:定义两个指针变量 p 和 q,初始时均指向头结点的下一个结点(链表的第一个结点)。p 指针沿链表移动;当 p 指针移动到第 k 个结点时,q 指针开始与 p 指针同步移动;当 p 指针移动到最后一个结点时,q指针所指示结点为倒数第 k 个结点。以上过程对链表仅进行一遍扫描。 
(2)算法的详细实现步骤: 
① count=0,p 和 q 指向链表表头结点的下一个结点; 
② 若 p 为空,转⑤; 
③ 若 count 等于 k,则 q 指向下一个结点;否则,count=count+1; 
④ p 指向下一个结点,转②; 
⑤ 若 count 等于 k,则查找成功,输出该结点的 data 域的值,返回 1;否则,说明 k 值超过了线性表的长度,查找失败,返回 0; 
⑥ 算法结束。
(3)算法实现:

typedef int ElemType; //链表数据的类型定义  
typedef struct LNode{ //链表结点的结构定义  
    ElemType data; //结点数据  
    struct Lnode *link; //结点链接指针  
} *LinkList;  
  
int Search_k(LinkList list, int k){   
    //查找链表 list 倒数第 k 个结点,并输出该结点 data 域的值  
    LinkList p = list->link, q = list->link; //指针 p、q 指示第一个结点  
    int count = 0;  
    while(p != NULL){ //遍历链表直到最后一个结点  
        if(count < k) count++; //计数,若 count < k 只移动 p  
        else q = q->link; p = p->link; //之后让 p、q 同步移动  
    }  
    if(count < k)  
        return 0; //查找失败返回 0  
    else { //否则打印并返回 1  
        printf("%d",q->data);   
        return 1;   
    }  
}  

提示:算法程序题,如果能够写出数据结构类型定义、正确的算法思想都会至少给一半以上分数,如果能用伪代码写出自然更好,比较复杂的地方可以直接用文字表达。 

 


登录后开始许愿

1 条上岸许愿
xia
2020年12月17日 15:40

666

赞(0)