数据结构——线性表

来源:互联网 发布:淘宝模特动作大全图片 编辑:程序博客网 时间:2024/04/29 18:11

线性表是数据结构的重要组成部分。数组和链表是线性表的两种常用表现形式,也是数据结构的基础。例如栈比较适用于数组描述,队列比较适用于链表描述,树叶比较适宜于链表描述。数据结构的数组描述和链表描述的基本操作都涉及建立、元素插入和删除操作等。本文主要介绍了线性表的链表形式,并通过丢手帕例子介绍了链表的建立、元素插入和元素删除过程。

 

1 线性表

由零个或多个数据元素组成的有限序列,元素可以是c++的基本数据类型,也可以是自定义的抽象数据类型。该序列中所含元素的个数称之为线性表的长度。线性表中元素在位置上是有序的。顺序表和链表是线性表的两种重要形式。线性表中基本操作是链表的建立、元素的插入和删除、求线性表的长度和根据元素索引寻找相应元素等。

 

2 顺序表

我们在学c基础知识的时候就接触了数组,但当时并没有涉及数据结构的概念,其实数组就是一种简单的线性表。虽然我们可以用一个数组存储若干线性表的实例,但通常我们都是用一个数组存储一个线性表实例。

顺序表的存储形式是在内存中占据了连续的内存空间。顺序表有三个属性:存储空间的起始位置、顺序表的最大存储容量和顺序表的当前长度。

 

3 链表

每个元素用一个单元或结点描述,每个结点有一个用来指向下一个元素的指针。链表是在存储形式上非连续、逻辑形式上连续的数据结构,通过链表中的指针链接次序实现的。配合c++的new和delete操作可以进行动态地插入/删除操作,增加/减小链表的长度。为了代码简洁和高效,有时候我们增加一个头结点放在链表最前面,它的数据域一般没有具体意义,指针域指向第一个节点。头结点不是必须的。

最常用的就是单链表,如果把链表的首尾链接起来,就是循环链表;如果每一个节点既有一个指向后一个元素的指针,又有指向前一个元素的指针,我们称它为双向链表

 

下面用一个实例,说明链表的建立、插入和删除等操作。引一个约瑟夫问题,丢手帕问题也是从这个演变来的。在解决这个典型问题中,就用到了链表的知识。据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

 

先看看下面这个程序吧。

#include<iostream>   using namespace std; class list          //定义一个类,这里也可以定义为结构体{public:    int No;         //定义整型成员变量No和list类型的指针变量next    list* next;}; int main(){    int number, base;                            //输入玩丢手帕游戏的总人数和出局数字base    cout<<"Pleaseinput the number:";    cin>>number;    cout<<endl;    cout<<"Pleaseinput the base number:";    cin>>base;    cout<<endl;     list *p, *head= new list;                 //定义两个list类型指针,其中head用于单链表完成后的首尾相连    head->No=0;    p=head;     int i;    for(i=1;i<=number;i++)                //建立链表,No为编号    {       list* node= new list;       node->No=i;       p->next=node;       p=p->next;          }    p->next=head->next;                  //链表首尾相连,此时的p仍然在建立的链表的最后一个节点     for(i=1;i<=number;i++)    {       p=p->next;                                 //第一次循环的时候p从链表最后一个节点到第一个节点,然后输出数据       cout<<p->No<<"  ";    }    cout<<endl;       p=head->next;                            //使p重新指向链表的第一个节点     list*temp= new list;            //new一个临时list类型指针,用于后面的删除(出局)操作    while(number>0)    {//下面的注释部分为,游戏设定出局数字为3,所以此段代码对于输入的base没影响    /*  temp= p->next->next;       p->next->next=temp->next;         p=temp->next;       delete[] temp;          number--;    */    //  for(i=1;i<base;i++)    //     p=p->next;       for(i=1;i<base-1;i++)                    //找到将要出局的那个人,p指向出局的那个人的前一个人,这里有一个bug,就base不能为1       p=p->next;    temp=p->next;                              //temp就是将要出局的那个人     p->next=temp->next;                   //越过出局的那个人,将链表链接起来    p=temp->next;                             //更新p所指的对象,p指向出局那个人的下一个人     delete[] temp;                              //删除出局的人    number--;                                     //出局后,人数减一       for(i=1;i<=number;i++)             //输出还剩下的人    {       cout<<p->No<<"  ";       p=p->next;    }    cout<<endl;  }    return0;}


 

上面这个例子涉及到了链表的建立、插入和删除及元素索引操作,通过这个例子对链表的操作进行综合练习。此例的难点在于通过for循环或者用多个p->next(例如p->next->next;)找到需要出局的人,然后重新连接链表并删除出局的人。关于链表的基础操作,读者可以在网上找些资料进行参考。

 

4 总结

顺序表和链表都是属于线性表,它们有各自的优点和缺点进行总结。

顺序表要求存储空间是一段连续的内存,在顺序表建立的时候要求确定线性表的容量,所以顺序表的优缺点也显而易见的。在寻找元素的时候可以根据下标快速找到,因此读取元素的时间复杂度为O(1),顺序表适用于索引元素操作;在插入/删除元素时,因为元素是连续存储的,所以要把插入/删除元素之后的元素整体向后/向前移动,因此删除和插入操作的时间复杂度为O(n),顺序表不适用于频繁地删除和插入操作。因为是顺序存储,所以没有类似链表的指向下一个元素的指针,存储当前元素的下一个内存空间就是下一个元素。

优点:

①可以快速存取表中任意位置元素;

②无须为表中元素之间的逻辑关系而增加额外的存储空间。

缺点:

①插入和删除操作移动大量元素;

②造成空间碎片。

 

链表不要求存储空间是一段连续的内存,也不要求在建立的时候确定线性表的容量,可以动态的增减长度,但必须有一个用来指向下一个元素的指针(单链表)。所以链表在寻找元素的时候必须从头结点开始逐个地向下进行,直到找到该元素或到尾结点。因此读取元素的时间复杂度为O(n),链序表不适用于频繁地索引元素操作;在插入/删除元素时,因为元素根据指针相连的,所以只要移动相应结点的指针,就可以完成插入/删除操作,而不影响其他元素,因此删除和插入操作的时间复杂度为O(1),链表适用于频繁地删除和插入操作。

优点:

①不需要预先分配内存空间,存储空间是动态增长的。

②可以快速地插入和删除元素;

缺点:

①索引元素较顺序表速度慢;

②每个元素需要额外的一个空间存储指向下一个元素的指针。

 

 

 

 


1 0
原创粉丝点击