期末复习之数据结构 第2章 线性表

目录

一.课本知识点 

1.线性结构

2.线性表

 3.线性表的顺序表示

 4.顺序表的基本操作

 5.线性表的链式表示

 6.链表的基本操作

总结 

 二.练习题


一.课本知识点 

1.线性结构

  • 定义:若结构是非空有限集,则有且仅有一个开始结点和一个终端结点,并且所有结点都最多只有一个直接前趋和一个直接后继
  • 可表示为:(a1 ,  a2   , ……,    an)是一个有序(次序)集 。     
    特点:     
    ① 只有一个首结点和尾结点;  
    ② 除首尾结点外,其他结点只有一个直接前驱和一个直接后继。
     
    简言之,线性结构反映结点间的逻辑关系是 一对一  的。

2.线性表

  • 定义: 用数据元素的有限序列表示例子:
     

  • 线性表的抽象数据类型定义:

  • 线性表的类型定义: 

    例子:
    //伪代码
    void union(List &La, List Lb) {
        La_len = ListLength(La);    // 求线性表La的长度
        Lb_len = ListLength(Lb);   // 求线性表Lb的长度
        for(i=1;i<=Lb_len; i++) {
            GetElem(Lb, i, e); // 取Lb中第i个数据元素赋给e
            if (!LocateElem ( La, e, equal( )) ) 
                ListInsert(La, ++La_len, e);
                  // La中不存在和 e 相同的数据元素,则插入之
            }//for
    } // union
    

 3.线性表的顺序表示

  • 线性表的顺序表示又称为顺序存储结构顺序映像
  • 顺序存储定义:逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构。
    简言之,逻辑上相邻,物理上也相邻
  • 顺序存储方法:用一组地址连续的存储单元依次存储线性表的元素,可通过数组V[n]来实现。
  • 顺序表的特点:
  1. 逻辑上相邻的数据元素,其物理上也相邻;
  2.  若已知表中首元素在存储器中的位置,则其他元素存放位置亦可求出(利用数组下标)。计算方法是(参见存储结构示意图):         
    设首元素a1的存放地址为
    LOC(a1)(称为首地址),       
    设每个元素占用存储空间(地址长度)为L字节,         
    则表中任一数据元素的
    存放地址为:      
    LOC(
    ai+1) = LOC(ai)+L      
    LOC(
    ai)    = LOC(a1) + L *(i-1
  • 顺序表的存储结构示意图:
  • 注意:

 4.顺序表的基本操作

  • 线性表的动态分配顺序存储结构:
    # define  LIST_INIT_SIZE   100       //符号常量   // 线性表存储空间的初始分配量
    # define   LISTINCREMENT    10     // 线性表存储空间的分配增量
    typedef struct {                                    //typedef 给结构类型起别名
           ElemType *elem;                          //表基址
           int                length;                      //表长(特指元素个数)
           int                listsize;                     //表当前存储容量
    }SqList
    
  •  线性表的初始化操作:
    Status InitList_Sq(SqList &L){
    	//构造一个空的线性表L
    	L.elem=(ElemType* )malloc(LIST_INIT_SIZE*sizeof(ElemType)); //为数据元素开辟一维数组空间
    	if (!L.elem)  exit(OVERFLOW); //存储分配失败
    	L.length=0;   // 空表长度为0
    	L.listsize=LIST_INIT_SIZE;   //初始存储容量
    	return OK;
    }
    
  • 线性表的销毁操作:

    	Status DestroyList(SqList &L)
    {
    	free(L.elem);
    	L.elem=NULL;
    	L.length=0;
    	L.listsize=0;
    	return OK;
    }
    
  • 线性表的清空操作:
    Status ClearList(Sqlist &L)
    {
        if(L.elem==NULL)
            exit(ERROR);
        int i;
        ElemType *p_elem = L.elem;
        for(i = 0;i < L.length;i++){
            *L.elem = NULL;
            L.elem++;
        }
        L.elem = p_elem;
        L.length = 0;
        return OK;
    }
  • 线性表的求长度操作:
    Status ListLength(SqList  L)
    {
    	 return L.length;
    }
    
  • 线性表元素的修改操作:
    status modify(SqList &L, int i, ElemType e){ //注意:i是位置		
    		if (i<1 || i>L.length) //判断位置i是否合法
            return ERROR;
            L.elem[i-1]=e; //或者 *(L.elem+i-1)=e	
            return OK;
    }
    
  • 线性表元素的插入操作:
    Status ListInsert_Sq(SqList &L,int i, ElemType e){
        if(i<1 || i>L.length+1) return ERROR
        if(L.length>=L.listsize){
            newbase=(ElemType )realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType))
            if(!newbase)exit(OVERFLOW);
            L.elem=newbase;  
            L.listsize+=LISTINCREMENT
        }
    }
    
  •  线性表元素的删除操作:
    Status Delete_Sq(SqList &L,int i){
        p=&L.elem[i-1]  
        e=*p
        q=&L.elem[L.length-1]
        //q=L.elem+L.length-1
        for (++p;p<=q;++p)
        *(p-1)=*p;  
        --L.length;
        return OK; 
    }

    时间效率分析:

 5.线性表的链式表示

  • 链式存储特点:逻辑上相邻,物理上不一定相邻
  • 链表存放示意图:
  • 每个存储结点都包含两部分:数据域指针域(链域) 。
  • 在单链表中,除了首元结点外,任一结点的存储位置由其直接前驱结点的链域的值指示。
  • 与链式存储有关的术语
  •  何谓头指针、头结点和首元结点?

  • 如何表示空表?
  • 头结点的数据域内装的是什么?
  • 循环链表:表中最后一个结点的指针域指向头结点,整个链表形成一个环。

  • 双向链表:结点有两个指针域,一个指向直接前驱,一个指向直接后继。 

 6.链表的基本操作

  • 单链表的读取(或修改)
    Status GetElem_L(LinkList L, int i, ElemType &e){
    // 注意:L为带头结点的单链表的头指针
    // 当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR
         P=L->next;      
         j=1;//j用来计数
         while(p && j<i)  {p=p->next;    ++j;}
         if (!p || j>i)   return ERROR;
    //!p即p为空,有两种情况,第一种是表为空;第二种是i的值超过了表的长度,while循环执行完后p走到最后一个元素还没有找到i的位置。j >i 就是i是0或者负数的情况。
         e=p->data;
         return OK;
    }
    
  • 单链表的插入

    Status ListInsert_L(LinkList &L,int i,ElemType e){
    		p=L; j=0;
    		while(p && j<i-1){p=p->next;++j}
    		                           //p将指向第i-1个元素
    		if(!p || j>i-1) return ERROR;
    
    		s=(LinkList)malloc(sizeof(LNode));
    		s->data=e; 
    
    		s->next=p->next;
    		p->next=s;
    
              return OK;
    	}
    
  • 单链表的删除
Status ListDelete_L(LinkList &L,int i,ElemType &e){
		p=L; j=0;
		while(p->next && j<i-1){p=p->next;++j}
		if(!p->next || j>i-1) return ERROR;
		//p指向第i-1个结点
		//判断p->next ,是要保证p的后面还有结点。
		q=p->next; 
		e=q->data; 
		p->next=q->next;
		free(q)
          return OK;
	}
  •  单链表的建立和输出
    头插法:每次都插入到头结点的后面,即链表的最前面。
    Void CreateList_L (LinkList &L, int n) {
      //逆位序输入n个元素的值,建立带表头结点的单链线性表L.
       L = (LinkList) malloc (sizeof(LNode));
       L→next = NULL;        // 先建立一个带头结点的单链表
       for ( i =n; i>0; --i) {
            p = (LinkList) malloc (sizeof(LNode)); // 生成新结点
            scanf (&p →data);       // 输入元素值
            p→next = L→next ;  
            L→next = p;              // 插入到表头
        }
    }  // CreateList_L
    
    尾插法:要有一个指针总是指向最后一个元素
    Void CreateList_L (LinkList &L, int n) {
      //正序输入n个元素的值,建立带表头结点的单链线性表L.
       L = (LinkList) malloc (sizeof(LNode));
       L→next = NULL;        // 先建立一个带头结点的单链表
      q=L;//q总是指向最后一个元素
       for ( i =n; i>0; --i) {
            p = (LinkList) malloc (sizeof(LNode)); //新结点
            scanf (&p →data);        p->next=null; // 输入元素值
            q→next = p;          //把新结点p放到q的后面
            q=q→next ;              
        }
    }  // CreateList_L
    
  •  求单链表的长度
    void len_LL (LinkList L,int &len){
             len=0;
    	LinkList p;
    	for(p=L->next;p!=NULL;p=p->next)
    	len++;
    }
    
  •  单链表的遍历
    void travle(LinkList L){
    	LinkList p
    	cout<<"建立的链表为:";
    	for(p=L->next;p!=NULL;p=p->next)
    		cout<<p->data<<"  ";
    	}
    
  •  双向链表的插入操作:
    Status ListInsert_DuL(DuLinkList &L,int i, ElemType e){
    If(!(p=GetElemP_DuL(L,i))) return ERROR;
    If(!(s=(DuLinkList)malloc(sizeof(DulNode)))) return ERROR;
    s->data=e;
    s->prior=p->prior;        p->prior->next=s;
    s->next=p;                    p->prior=s;
    return Ok;
    }
    
  • 双向链表的删除操作:

    Status ListDelete_DuL(DuLinkList &L,int i, ElemType &e){
    If(!(p=GetElemP_DuL(L,i))) return ERROR;
    e=s->data;
    p->prior->next=p->next;
    p->next->prior= p->prior;
    free(p); return Ok;
    }
    

     时间效率分析

     空间效率分析


    讨论1:线性表的逻辑结构特点是什么?其顺序存储结构和链式存储结构的特点是什么?

     讨论2:顺序存储和链式存储各有哪些优缺点?

     讨论3:在什么情况下用顺序表比链表好?

  • 总结 

    顺序表

    链表

    基于空间考虑

    分配方式

    静态分配。程序执行之前必须明确规定存储规模,若线性表长度n变化较大,则存储规模难于预先确定,估计过大将造成空间浪费,估计过小又将使空间溢出机会增多。 

    动态分配。只要内存空间尚有空闲就不会产生溢出,因此当线性表的长度变化较大,难以估计其存储规模时,以采用动态链表作为存储结构为好。

    存储密度

    =1.当线性表的长度变化不大,易于事先估计其大小时,为了节约存储空间,宜采用顺序存储作为存储结构。

    <1

    基于时间考虑

    存取方法

    随机存取结构。对表中任一结点都可在O(1)时间内直接取得。若线性表的操作主要是进行查找,很少做插入和删除操作,则采用顺序表作为存储结构为宜。

    顺序存取结构。链表中的结点需从头指针起顺着链扫描才能取得。

    插入删除操作

    在顺序表中进行插入和删除操作,平均要移动表中一半的结点,尤其是每个结点的信息量较大时,移动结点的开销就相当可观。

    在链表中的任何位置插入和删除,都只需要修改指针。对于频繁进行插入和删除操作的线性表,宜采用链表作存储结构。若表的插入和删除主要发生表的首尾两端,则采用尾指针表示的单循环链表为宜。

 二.练习题

题组一:

题组二: 

一、填空

1. 在顺序表中插入或删除一个元素,需要平均移动       n/2或n-1/2   元素,具体移动的元素个数

   插入或删除元素的位置       有关。

2. 向一个长度为n的向量的第i个元素(1≤i≤n+1)之前插入一个元素时,需向后移动              

 n-i+1     个元素。

3. 向一个长度为n的向量中删除第i个元素(1≤i≤n)时,需向前移动         n-i    个元素。

4.  在单链表中,除了首元结点外,任一结点的存储位置由    前驱结点的后继指针   指示。

二、简答题

1. 试比较顺序存储结构和链式存储结构的优缺点。在什么情况下用顺序表比链表好?

顺序表 优点:存储密度大 存储空间利用率高

         缺点:插入或删除元素时不方便

链式存储 优点:插入或删除元素时很方便

        缺点:存储密度小 存储空间利用率低

在插入和删除情况比较少的情况下用顺序表比链表好

2 .  描述以下三个概念的区别:头指针、头结点、首元结点(第一个元素结点)。在单链表中设置头结点的作用是什么?

头指针: 指向链表第一个结点的指针

头节点:在首元结点之前附设的一个结点,该结点不存储数据元素,其指针指向首元节点,其作用主要是为了方便对链表的操作

首元结点:链表中存储第一个数据元素的结点

三、线性表具有两种存储方式,即顺序方式和链接方式。现有一个具有五个元素的线性表L={23,17,47,05,31},若它以链接方式存储在下列100~119号地址空间中,每个结点由数据(占2个字节)和指针(占2个字节)组成,如下所示:

05

U

17

X

23

V

31

Y

47

Z

^

^

100

120

其中指针X,Y,Z的值分别为多少?该线性表的首结点起始地址为多少?末结点的起始地址为多少?

X:116 Y:NULL Z:100

首结点起始地址108  末结点起始地址为112

四、编程题

1.  写出在顺序存储结构下将线性表逆转的算法,要求使用最少的附加空间。

void Reverse(SqList& L) {

for (int i = 0; i <= (L.length - 1) / 2; i++) {

int temp = L.data[i];

L.data[i] = L.data[length - 1-i];

L.data[length-1-i] = temp;

}

}

  1. 编写程序,将若干整数从键盘输入,以单链表形式存储起来,然后计算单链表中结点的个数(其中指针P指向该链表的第一个结点)。

  

#include <iostream>

using namespace std;

#define ElemType int

#define Status int

#define MAXSIZE 100



typedef struct LNode

{

ElemType data;//数据域

struct LNode* next;//指针域

}LNode, * LinkList;



Status InitList(LinkList& L) {

L = new LNode;

L->next = NULL;

return 1;

}



Status InsertList(LinkList& L, int pos, ElemType e) {

LNode* s;

LinkList p = L;

int j = 0;

while (p&&(j<pos-1))

{

p = p->next;

++j;

}

if (!p || j > pos - 1) {

return 0;

}

s = new LNode;

s->data = e;

s->next = p->next;

p->next = s;

return 1;

}



int main() {

LinkList P;

InitList(P);

int num;

cout << "请输入整数,按ctrl+z结束输入" << endl;

int Length = 1;

while (cin>>num)

{

Length++;

InsertList(P, Length, num);

}

cout <<"单链表结点个数为:"<< Length-1 << endl;

}

题组三:

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值