1、2 线性表,2/49,线性表,主要知识点,顺序表,单链表,循环单链表,循环双向链表,应用举例,双链表,3/49,唯一头元素,唯一尾元素,除头元素外,均有一个直接前驱,除尾元素外,均有一个直接后继,2.1 线性表的概念,4/49,其中a1是头元素, an是尾元素, ai是第i个元素。,ai-1是ai的直接前驱, ai是ai-1的直接后继。,当2in时, ai有且只有一个直接前驱。,当1in-1时, ai有且只有一个直接后继。,2.1 线性表的概念,5/49,线性表的存储,顺序存储 顺序表链式存储 链表,6/49,用一组地址连续的存储单元依次存储线性表的数据元素。,设每个元素需占用 l 个存储单元
2、,LOC(ai)表示元素ai的存储地址,则LOC(a1)是第一个数据元素a1的存储地址,也是整个线性表的起始地址,LOC(ai+1) = LOC(ai) + l,LOC(ai) = LOC(a1) + (i - 1)l,2.2 顺序表,7/49,算法2.1 在第 i 个数据元素之前插入一个新的元素,思想:,1. 将第 n 到 i 个元素均向后移动一个位置。,2. 将新元素放置在第 i 个位置。,例,在第 i 个元素前插入 b,b,8/49,例,在第4个元素之前插入元素25。,25,9/49,算法时间复杂度:,移动元素的个数取决于插入元素位置。,i=1,需移动 n 个元素;,i=i,需移动 n
3、i +1 个元素;,i=n+1,需移动 0 个元素;,10/49,假设pi是在第 i 个元素之前插入一个新元素的概率,11/49,算法2.2 删除第 i 个数据元素,思想:,1. 删除第 i 个数据元素。,2. 将第 i+1 到 n 个元素均向前移动一个位置。,12/49,例,删除第4个元素25。,13/49,算法时间复杂度:,移动元素的个数取决于删除元素位置。,i=1,需移动n - 1个元素;,i=i,需移动n i 个元素;,i=n,需移动0个元素;,14/49,假设qi是删除第 i 个元素的概率,15/49,小结,顺序表中按位置读取元素十分方便,其时间代价为O(1);其余操作包括插入、删除
4、和按内容查找的时间复杂度均为O(n)。,16/49,可随机存取表中任意数据元素,算法简单,空间单元利用率高;,优点:,直接可获取线性表的长度,例,L.elemi-1表示第 i 个数据元素,例,L.length表示线性表长度,缺点:,数据元素的插入、删除相对麻烦,需要预先确定数据元素的最大个数,插入和删除时需要移动较多的数据元素。,顺序表特点,17/49,2.3 链表(Linked List),通过指针来链接结点的存储方式。 利用指针来表示数据元素之间的逻辑关系 逻辑上相邻的元素在物理位置上不要求也相邻 按照需要为表中新的元素动态地分配存储空间,动态改变长度 根据链接方式和指针多寡 单链表 双链
5、表 循环链表,18/49,链表的运算,检索: 在链表中查找满足某种条件的元素 插入 : 在链表的适当位置插入一个元素 删除: 从链表中删除一个指定元素,19/49,19,单链表(singly linked list),通过指针把它的一串存储结点链接成一个链 存储结点由两部分组成: 数据字段 + 指针字段(后继地址),20/49,20,单链表的存储结构,21/49,21,单链表的结点类型,template class Link public: T data; / 用于保存结点元素的内容Link *next; / 指向后继结点的指针Link(const T info, const Link* ne
6、xtValue = NULL) data = info;next = nextValue;Link(const Link* nextValue) next = nextValue; ;,22/49,22,单链表的定义,template class lnkList : public List private: Link * head, *tail; / 单链表的头、尾指针Link *setPos(const int p); / 返回线性表指向第p个元素的指针值 public: lnkList(int s); / 构造函数lnkList(); / 析构函数bool isEmpty(); / 判断链
7、表是否为空void clear(); / 将链表存储的内容清除,成为空表int length(); / 返回此顺序表的当前实际长度bool append(cosnt T value); / 在表尾添加一个元素value,表的长度增1bool insert(cosnt int p, cosnt T value); / 在位置p上插入一个元素value,表的长度增1bool delete(cosnt int p); / 删除位置p上的元素,表的长度减 1bool getValue(cosnt int p, T / 查找值为value的元素,返回第1次出现的位置 ,23/49,23,查找单链表中第i
8、个结点,/ 函数返回值是找到的结点指针 template / 线性表的元素类型为T Link * lnkList : setPos(int i) int count = 0;if (i = -1) / i为-1则定位到头结点return head;/ 循链定位,若i为0则定位到第一个结点 Link *p = head-next;while (p != NULL /指向第 i 结点,i0,1,,当链表中结点数小于i时返回NULL ,24/49,24,单链表的插入,在23 和12 之间插入 10,创建新结点 新结点指向右边的结点 左边结点指向新结点,25/49,25,单链表插入算法,/ 插入数据内
9、容为value的新结点作为第i个结点 template / 线性表的元素类型为T bool lnkList : insert(const int i, const T value) Link *p, *q;if (p = setPos(i -1) = NULL) / p 是第i个结点的前驱cout (value, p-next); /新节点指向右边节点p-next = q; /左边节点指向新节点if (p = tail) / 插入点在链尾,插入结点成为新的链尾tail = q; return true; ,26/49,26,x,p,p next = q next;,q,q = pnext;,f
10、ree(q);,删除值为 x 的结点,27/49,27,template / 线性表的元素类型为T bool lnkList: delete(const int i) Link *p, *q; / 待删结点不存在,即给定的i大于当前链中元素个数if (p = setPos(i-1) = NULL | p = tail) cout next; / q是真正待删结点if (q = tail) / 待删结点为尾结点,则修改尾指针tail = p;p-next = NULL:delete q;else if (q != NULL) / 删除结点q 并修改链指针 p-next = q-next;dele
11、te q;return true; ,单链表删除算法,28/49,28,求长度算法,int length() Link *p = head-next;int count = 0;while (p != NULL) p = p-next;count+;return count; ,29/49,29,单链表上运算的分析,1. 对一个结点操作,必先找到它,即用一个指针指向它 2. 找单链表中任一结点,都必须从第一个点开始: p = head;while (没有到达)p = p-next;,单链表的时间复杂度 O(n) 定位: O(n) 插入: O(1) 删除: O(1),30/49,30,双链表(d
12、ouble linked list),为弥补单链表的不足,而产生双链表 单链表的next字段仅仅指向后继结点,不能有效地找到前驱, 反之亦然 增加一个指向前驱的指针,31/49,31,双链表及其结点类型的说明,template class Link public: T data; / 用于保存结点元素的内容Link * next; / 指向后继结点的指针Link *prev; / 指向前驱结点的指针/ 给定值和前后指针的构造函数Link(const T info, Link* preValue = NULL, Link* nextValue = NULL) data = info;next =
13、 nextValue;prev = preValue;/ 给定前后指针的构造函数Link(Link* preValue = NULL, Link* nextValue = NULL) next = nextValue;prev = preValue; ,32/49,32,双链表的插入,如果要在p所指结点后插入一个新结点 执行new q开辟结点空间; 让该新结点的next填入p所指的后继地址; 新结点的prev填入p所指结点的后继的prev字段;new q;q-next = p- next;q-prev = p- next -prev; 把新结点的地址填入原p所指结点的next字段; 新结点后继
14、结点的prev字段也应该回指新结点p- next = q;q- next -prev = q;,33/49,33,ki-1,ki,ki+1,p,pnext ,q,x,q prev = p;,q next = p next;,p next prev = q;,p next = q;,双链表插入示意,34/49,34,双链表的删除,如果要删除指针变量 p 所指的结点,只需修改该结点前驱的 next 字段和该结点后继的prev字段,即 p- prev - next = p- next; p- next - prev = p- prev;然后把变量p变空,把p所指空间释放即可 p- next = NU
15、LL; p-prev = NULL; delete p;,35/49,35,ki-1,ki,ki+1,p,p prev ,p next ,p prev next = p next,p next prev = p prev,prev,next,双链表删除示意,36/49,36,循环链表(circularly linked list),将单链表或者双链表的头尾结点链接起来,就是一个循环链表 不增加额外存储花销,却给不少操作带来了方便 从循环表中任一结点出发,都能访问到表中其他结点,37/49,37,几种链表比较,38/49,38,链表的边界条件,几个特殊点的处理 头指针处理 非循环链表尾结点的指针
16、域保持为NULL 循环链表尾结点的指针回指头结点链表处理 空链表的特殊处理 插入或删除结点时指针勾链的顺序 指针移动的正确性 插入 查找或遍历,39/49,39,线性表实现方法的比较,顺序表的主要优点 没有使用指针,不用花费额外开销 线性表元素的读访问非常简洁便利 链表的主要优点 无需事先了解线性表的长度 允许线性表的长度动态变化 能够适应经常插入删除内部元素的情况,顺序表是存储静态数据的不二选择 链表是存储动态变化数据的良方,40/49,40,顺序表和链表的比较,顺序表 插入、删除运算时间代价O(n),按位置查找则可常数时间完成 预先申请固定长度的数组 如果整个数组元素很满,则没有结构性存储
17、开销 链表 插入、删除运算时间代价O(1),但找第i个元素运算时间代价O(n) 存储利用指针,动态地按照需要为表中新的元素分配存储空间 每个元素都有结构性存储开销,41/49,41,顺序表和链表存储密度,n表示线性表中当前元素的数目, P表示指针的存储单元大小(通常为4bytes) E表示数据元素的存储单元大小 D表示可以在数组中存储的线性表元素的最大数目 空间需求 顺序表的空间需求为DE 链表的空间需求为n(P+E) n的临界值,即n DE/(P+E) n越大,顺序表的空间效率就更高 如果P = E,则临界值为n = D/2,42/49,42,应用场合的选择,顺序表不适用的场合 经常插入删除
18、时,不宜使用顺序表 线性表的最大长度也是一个重要因素 链表不适用的场合 当读操作比插入删除操作频率大时,不应选择链表 当指针的存储开销,和整个结点内容所占空间相比其比例较大时,应该慎重选择,43/49,43,顺序表和链表的选择,顺序表 结点总数目大概可以估计 线性表中结点比较稳定(插入删除少) n DE/(P+E) 链表 结点数目无法预知 线性表中结点动态变化(插入删除多) n DE/(P+E),44/49,44,小结,44,顺序表 按索引值从小到大存放在一片相邻的连续区域 紧凑结构,存储密度为1链表 单链表 双链表 循环链表,45/49,应用-跳舞链(Dancing Links),A,C,B
19、,1. p-prev-next = p-next ;,2. p-next-prev = p-prev ;,如何恢复到链表中呢?,操作 (1),操作 (2),注意到删除p结点时,我们未将p结点的左右指针清空,那么将p恢复到链表的操作将变得与(1)操作一样简单: p-prev-next = p, p-next-prev = p。,46/49,Dancing Links,分析 注意到p在删除时,左右指针未被清空是不符合正常程序的思想 (野指针问题) 但在这里,这样的野指针,却保证了在双向链表中的删除与恢复操作。 操作(2)的优雅之处就在于我们只要知道p,就可以恢复操作(1)。,47/49,Danci
20、ng Links之应用,那么如何利用操作(2)的性质 idea 1:注意到一些交互式的程序中,用户经常对于先前所做的操作进行撤消。 idea 2:注意到一类搜索问题中(例如N皇后问题)进行的试探-回溯,需要大量的删除与恢复的操作。 idea 1显然过于直白,对操作(2)的性质没有深入的认识。而idea 2则充分运用了操作(1)与操作(2)的互动性,在执行搜索的试探-回溯中,指针在数据结构内部被灵活地运用如同精巧的舞蹈(Dancing)。 因此这对基于双向链表(Links)的操作(1)与操作(2)在搜索技术上的互动应用,被Knuth定义为Dancing Links。,48/49,Dancing Links,更多的细节,参见http:/www-cs-faculty.stanford.edu/uno/papers/dancing-color.ps.gz,49/49,作 业,书面作业(下周一交): 设计一个算法,删除一个顺序表中从第i个元素开始的k个元素。 设计一个非递归算法在O(n)时间内将一个含有n个元素的单链表逆置,要求其辅助空间为常量。 定义双向链表类,实现在某个元素之前插入一个元素的操作。,