数据结构导论(第二章线性表)

一、线性表的基本概念

线性表的逻辑结构 线性表是一种最简单、最常见的数据结构

线性表是由n(n≥0)个数据元素(结点)a1,a2,a3,……an组成的有限序列。 数据元素的个数n定义为表的长度。 当n=0时,称为空表

将非空的线性表(n>0)记作:L=(a1,a2,a3,……,an) a1:起始结点,an:终端结点。 a1称为a2的直接前驱,a3称为a2的直接后继

线性表的特点:

  • 线性表中只有1个起始结点,1个终端结点, 起始结点没有直接前驱,有1个直接后继。
  • 终端结点有1个直接前驱,没有直接后继。
  • 除这2个结点外,每个结点都有且只有1个直接前驱和1个直接后继

二、线性表的顺序存储

线性表顺序存储的方法:将表中的结点依次存放在计算机内存 中的一组连续存储单元中。

数据元素在线性表中的邻接关系决定其在存储空间中的存储位置;即逻辑结构中相邻的结点,其存储位置也相邻。

 用顺序存储实现的线性表称为顺序表。

顺序表上的基本运算 : 插入(Insert)、删除(Delete)、定位(Locate)

=======================插入=========================

线性表的插入运算:是指在表的位置i上,插入一个新结点x,使 长度为n的线性表(a1,a2,……,ai,……,an) 变为长度为n+1的线性表(a1,……,x,……,an)

插入时的主义事项:

  • 当表空间已满,不可再做插入操作。
  • 当插入位置是非法位置,不可做正常的插入操作。

顺序表插入操作过程

  • 将表中位置为n ,n-1,…,i上的结点,依次后移到位置n+1,n,…,i+1 上,空出位置i。
  • 在位置i上插入新结点x。 (当插入位置i=n+1时,无须移动结点,直接将x插入表的末尾)
  • 该顺序表长度加1

插入算法的分析:

  • 假设线性表中含有n个数据元素,在进行插入操作时,有n+1个位置可插入,每个位置插入数据的概率是:1/(n+1); 在位置i插入时,要移动n-i+1个数据。
  • 假定在n+1个位置上插入元素的可能性均等, 则平均移动元素的个数为:

  

 

 需要移动:10-4+1=7 ;7个节点

======================删除=====================

线性表的删除运算:是指将表的位置i的结点删去,使长度为n的线性 表(a1,a2,……,ai,……,an)变成长度为n-1的线性表 (a1,a2,……,ai-1,ai+1,……,an)

注意事项:当要删除元素的位置i不在表长范围内时,为非法位置,不能做正 常的删除操作

顺序表删除操作过程

  • 若i=n,则只要删除终端结点,无须移动结点;
  • 若1≤i≤n-1,则必须将表中位置 i+1,i+2,……,n的结点,依次前移到 位置i,i+1,…,n-1上,以填补删除操作造成的空缺。 (仅当删除位置i=n时, 才无须移动结点,直接令表长度-1即可)
  • 该表长度减1

假设线性表中含有n个数据元素,在进行删除操作时,有n个位置可删除, 在位置i删除时,要移动n-i个数据。

 

 

 

 结论:顺序存储结构表示的线性表,在做插入或删除操作时,平均 需要移动大约一半的数据元素。当线性表的数据元素量较大, 并且经常要对其做插入或删除操作时,这一点需要考虑

===================定位=====================

定位:从第一个元素a1起,依次和x比较, 直到找到一个与x相等的数据元素,则 返回它在顺序表中的存储下标或序号;当查遍整个表都没找到与x相等的元 素,返回0。

定位算法的分析:

定位运算的功能是查找出线性表L中值等于x的结点序号的最小值,当不存 在这种结点时,结果为0。

i从0开始,作为扫描顺序表时的下标: 

  • 最好情况下,第1个元素就是x值,此时查找比较次数为1。 
  • 最坏情况下,最后1个元素是x值,此时查找比较次数为n。 
  • 故平均查找长度为(n+1)/2。

顺序表的特点:

  • 设线性表中所有结点的类型相同,则每个结点所占用 存储空间大小亦相同。
  • 假设表中每个结点占用L个存储单元,其中第一个单元 的存储地址则是该结点的存储地址。
  • 并设表中开始结点a1的存储地址是d,那么结点ai的存 储地址LOC(ai)

 顺序表的优点:

  • 无需为表示结点间的逻辑关系而增加额外存储空间 
  • 可以方便地随机存取表中的任一结点

顺序表的缺点:

  • • 插入和删除运算不方便,必须移动大量的结点
  • • 顺序表要求占用连续的空间,存储分配只能预先进 行,因此当表长变化较大时,难以确定合适的存储 规模

三、线性表的链式存储

结点结构:

 链表的具体存储表示为:

  • 用一组任意的存储单元来存放。
  • 链表中结点的逻辑次序和物理次序不一定相同,还必须存储指示其后继 结点的地址信息

=================单链表===================

 

  • 单链表:所有结点通过指针链接而组成。 
  • NULL:空指针。
  • Head:头指针。
  • 尾指针:指向最后一个元素(尾结点)的指针。

单链表的一般图示法:由于我们常常只注重结点间的逻辑顺序,不关心每个结点的实际位置,可以用 箭头来表示链域中的指针

单链表各个结点在内存中的存储位置(物理位置)并不一定连续

 

 头结点:一般不存数据,利用Head存放该结点地址。在单链表中,增加头结点的目的是方便运算

单链表特点总结:

  • 链表由头指针唯一确定,单链表可以用头指针的名字来命名。头指针名是head3的链表可称为表head3。 
  • 起始节点又称为首结点,无直接前驱,故在无头结点时,设头指针head指向首结点。
  • 终端结点又称尾结点,无直接后继,故终端结点的指针域为空,即NULL。 
  • 除头结点之外的结点为表结点,为运算操作方便,头结点中不存数据。

 

 单链表的实现:

 

 

1、初始化

  • 建立一个空的单链表L,InitiateLinkList(L) 一个空的单链表是一个头指针和一个头结点构成的
  • 假设已定义指针变量t,令t指向一个头结点 并令头结点的next为NULL 
  • 注意:产生头结点时由malloc函数产生一个新节点; 动态分配内存函数malloc函数格式:(数据类型*)malloc(sizeof(数据类型))  如:int *p;p=(int *)malloc(sizeof(int))
空表由一个头指针和一个头结点组成。算法描述如下:
LinkList InitiateLinkList( )
//建立一个空的单链表
{
LinkList head; //头指针
head=malloc (sizeof (Node) ) ; //动态构建一结点,它是头结点
head->next=NULL;
return head;
}
在算法中,变量head是链表的头指针,它指向新创建的结点,即头结点。一个空单链表仅有一个头结点,它的指针域为NULL。

2、求表长

在单链表存储结构中,线性表的长度等于单链表所 含结点的个数(不含头结点)

步骤:

  • 1,令计数器j为0
  • 2,令p指向头结点
  • 3,当下一个结点不空时,j加1,p指向下一个结点
  • 4,j的值即为链表中结点个数,即表长度
int lengthLinklist (LinkList head){ 
    Node *p;
    p=head; j=0;
    while( p->next != NULL )
    { p=p->next;
    j++;
    }
    return(j);
}

3、读表元素步骤:查找第i个结点

  • 1、令计数器j为0
  • 2、令p指向头结点
  • 3、当下一个结点不空时,并且j<i时,j加1,p指向下一个结点
  • 4、如果j等于i,则p所指结点为要找的第i结点否则,链表中无第i结点
Node * GetlinkList( LinkList head, int i ){ 
    Node *p;
    p=head->next; int c=1;
    while ((c<i) && (p!=NULL) ){ p=p->next;
        c++;
    }
    if(i= =c) return(p);
    else return NULL;
}

4、删除节点

算法思路(此算法描述删除第i个结点)

① 找到第i-1个结点;若存在继续,否则结束;

② 删除第i个结点,并释放对应的内存,结束

删除运算是将表的第i个结点删去。

(1)找到ai-1的存储位置p

(2)令p->next指向ai的直接后继结点

(3)释放结点ai的空间,将其归还给"存储池" 。

算法的实现

在单链表中删除第 i 个结点的基本操作为:找到线 性表中第i-1个结点,修改其指向后继的指针。

 

 

void DeleteLinklist(LinkList head, int i)
    //删除表head的第i个结点
    {
    Node *q;
    if(i==1) q=head;
    else q=GetLinklist(head, i-1); //先找待删结点的直接前驱
    if(q !== NULL && q->next != NULL) //若直接前驱存在且待删结点存在
    {
        p=q->next; //p指向待删结点
        q->next=p->next; //移出待删结点
        free(p); //释放已移出结点p的空间
    }
    else exit (“找不到要删除的结点”); //结点不存在
}

注意:free(p)是必不可少的,因为当一个结点从链表移出后,如果不释放它的空间,它将变 成一个无用的结点,它会一直占用着系统内存空间,其他程序将无法使用这块空间

 5. 定位

 定位运算是对给定表元素的值,找出这个元素的位置。对 于单链表,给定一个结点的值,找出这个结点是单链表的 第几个结点。定位运算又称为按值查找

 具体步骤:

  • 1、令p指向头结点
  • 2、令i=0
  • 3、当下一个结点不空时,p指向下一个结点,同时i的值加1
  • 4、直到p指向的结点的值为x,返回i+1的值。
  • 5、如果找不到结点值为x的话,返回值为0

 

int LocateLinklist(LinkList head, DataType x)
    //求表head中第一个值等于x的结点的序号,若不存在这种结点,返回结果为0
    {
    Node *p=head; //p是工作指针
    p=p->next; //初始时p指向首结点
    int i=0; //i代表结点的序号,这里置初值为
    while (p != NULL && p->data != x) //访问链表
    {
    i++;
    p=p->next;
    }
    if (p!=NULL) return i+1;
    else return 0;
}

6、插入

 插入运算是将值为x的新结点插入到表的第i个结点 的位置上,即插入到ai-1与ai之间。

具体步骤:

  • (1)找到ai-1存储位置p
  • (2)生成一个数据域为x的新结点*s
  • (3)令结点*p的指针域指向新结点
  • (4)新结点的指针域指向结点ai

 

 

 

void InsertLinklist (LinkList head, DataType x, int i)
    //在表head的第i个数据元素结点之前插入一个以x为值的新结点
    {
    Node *p,*q;
    if (i==1) q=head;
    else q=GetLinklist (head, i-1); //找第 i-1个数据元素结点
    if (q==NULL) //第i-1个结点不存在
    exit(“找不到插入的位置”);
    else
    {
        p=malloc(sizeof (Node) );p->data=x; //生成新结点
        p->next=q->next; //新结点链域指向*q的后继结点
        q->next=p; //修改*q的链域
    }
}

注意:链接操作p->next=q->next和q->next=p两条语句的执行顺序不能颠倒,否则结点 *q的链域值(即指向原表第i个结点的指针)将丢失。

7、清除单链表中值为x的重复结点

步骤:

  • 1)找到值为x的第一个结点位置,p指向该结点
  • 2)从p所指结点开始向后查找, 若存在值为x的结点,令q指向x结点前一个 执行删除操作 继续查找直到链表末尾
void PurgeLinklist(LinkList head)
    //删除表head中多余的重复结点
    {
        Node *p,*q,*r;
        q=head->next; //q指示当前检查结点的位置,置其初值指向首结点
        while(q!=NULL) //当前检查结点*q不是尾结点时,寻找并删除它的重复结点
    {
        p=q; //工作指针p指向*q
        while(p->next!=NULL) //当*p的后继结点存在时,将其数据域与*q数据域比较
        { if (p->next->data==q->data) //若*(p->next)是*q的重复结点
            {
                r=p->next; //r指向待删结点
                p->next = r->next; //移出结点* (p->next),p->next指向原来* (p->next)的后继结点
                free (r);
            }
            else p=p->next; //否则,让p指向下一个结点
        }
            q=q->next; //更新检查结点
    }
}

 四、顺序表的算法

线性表的基本运算

1,初始化 Initiate(L)     建立一个空表L=(),L不含数据元素。  

2,求表长度 Length(L)   返回线性表L的长度。

3、取表元 Get(L,i)    返回线性表第i个数据元素,当i不满足 1≤i≤Length(L)时,返回一特殊值  

4、定位 Locate(L,x)   查找线性表中数据元素值等于x的结点序号,若有多 个数据元素值与x相等,运算结果为这些结点中序号 的最小值,若找不到该结点,则运算结果为0  

5、插入 Insert(L,x,i)  在线性表L的第i个数据元素之前插入一个值为x的新数据 元素,参数i的合法取值范围是1≤i≤n+1。操作结束后线性 表L由(a1,a2,…,ai-1, ai,ai+1,.…,an )变为(a1,a2, …,ai-1,x, ai,ai+1,.…,an ),表长度加 1。

6、删除 Delete(L,i)  删除线性表L的第i个数据元素ai,i的有效取值范围是1≤i≤n。 删除后线性表L由(a1,a2,…,ai-1, ai,ai+1,.…,an )变为 (a1,a2,…,ai-1,ai+1,.…,an ),表长度减1

五、其它链表 

1、循环链表

  • 普通链表的终端结点的next值为NULL
  • 循环链表的终端结点的next指向头结点
  • 在循环链表中,从任一结点出发能够扫描整个链表

如何找到循环链表的尾结点

在循环链表中附设一个rear指针指向尾结点 适用于经常适用头尾结点的链表操作中

 

 2、双向循环链表

在链表中设置两个指针域, 一个指向后继结点 一个指向前驱结点 这样的链表叫做双向链表

 

 

 

 

 七、顺序实现与链式实现的比较

线性表与链表的优缺点:

(1)单链表的每个结点包括数据域与指针域,指针域需要占用额外空间。

(2)从整体考虑,顺序表要预分配存储空间,如果预先分配得过大,将造成 浪费,若分配得过小,又将发生上溢;单链表不需要预先分配空间,只 要内存空间没有耗尽,单链表中的结点个数就没有限制。

 

 

posted @ 2019-10-29 20:33  小禾点点  阅读(1184)  评论(0编辑  收藏  举报