第十五节 线性树形结构
基础知识
线性表
线性表是指由n个具有相同特性的数据元素组成的有限序列,是最基本、最简单,也是最常用的一种数据结构。队列、栈、链表、哈希表等数据结构逻辑上都属于线性表。一般来讲,表中数据之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其他数据元素都是首尾相接的。
特性
- 线性表中的数据元素的个数n定义为线性表的长度,n=0时称为空表。
- 非空表中必定存在第一个元素和最后一个元素
- 非空表中的元素,除了最后一个元素外,均有唯一的后继;除了第一个元素外,均有唯一的前驱
逻辑结构
物理结构
线性表可由顺序存储或链式存储表示,实际应用中常以栈、队列、字符串等特殊形式使用。
操作
- C r e a t e ( L , l e n g t h ) : Create(L,length): Create(L,length):建立长度为 l e n g t h length length的线性表。
- I n i t ( L ) : Init(L): Init(L):初始化线性表为空。
- C l e a r ( L ) : Clear(L): Clear(L):清空数据元素。
- I s E m p t y ( L ) : IsEmpty(L): IsEmpty(L):如果表 L L L为空则返回 t r u e true true,否则返回 f a l s e false false。
- L e n g t h ( L ) : Length(L): Length(L):返回表 L L L的长度,即表中元素的个数。
- G e t ( L , i ) : Get(L,i): Get(L,i):获取表 L L L中位置为 i i i处的元素 ( 1 ≤ i ≤ n ) (1≤i≤n) (1≤i≤n)。
- P r i o r ( L , i ) : Prior(L,i): Prior(L,i):取 i i i的前驱元素。
- N e x t ( L , i ) : Next(L,i): Next(L,i):取 i i i的后继元素。
- I n s e r t ( L , i , x ) : Insert(L,i,x): Insert(L,i,x):在表 L L L的位置 i i i处插入元素 x x x,后面的元素都向后挪一个位置。
- D e l e t e ( L , p ) : Delete(L,p): Delete(L,p):从表 L L L中删除位置 p p p处的元素。
- U p d a t e ( L , i , v ) : Update(L,i,v): Update(L,i,v):修改表 L L L中 i i i位置的元素为 v v v。
- L o c a t e ( L , x ) : Locate(L,x): Locate(L,x):获取元素 x x x在表 L L L中的位置。
- T r a v e r s e ( L ) : Traverse(L): Traverse(L):遍历输出所有元素。
- S o r t ( L ) : Sort(L): Sort(L):对表 L L L中的所有元素排序。
队列
队列(queue)是一种特殊的线性表,只允许在表的头部(队首 )进行删除操作,只允许在表的尾部(队尾 )进行插入操作,是一种操作受限制的线性表。在队列中插入一个队列元素称为入队 操作,从队列中删除一个队列元素称为出队 操作。
特性
因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出 (FIFO,first in first out)线性表。
逻辑结构
物理结构
队列可以用顺序存储方式实现,借助数组可简单模拟一个队列,但数组空间难以确定,太小会导致空间不足,太多会造成空间浪费。
借助链式存储的队列无须确定空间大小,虽然创建、插入和删除节点较为烦琐,但可以实现空间的动态增长。
操作(以STL中的queue库为例)
cpp
#include<queue>
queue <int> q; //新建队列q,队列中的元素类型为int
q.size(); //返回队列长度
q.empty(); //判断队列是否为空,空则返回true
q.push(node); //入队操作,node入队
q.pop(); //出队操作
q.front(); //返回队首元素
q.back(); //返回队尾元素
栈
栈(stack)也是一种操作受限的线性表,只能在表的一端进行插入和删除操作,这一端称为栈顶 ,相对的另一端称为栈底 。向栈内添加新元素的操作称为入栈 、进栈、压栈,删除元素称为出栈 、退栈。
特性
由于栈只允许在一端进出,所以只有最先进入队列的元素反而最后才能出去,故栈又称为先进后出 (FILO,first in last out)线性表。
逻辑结构
物理结构
和队列一样,栈也可以用顺序结构存储,并使用数组简单模拟,但会有空间浪费。使用链式存储虽然烦琐,但优点还是可以实现空间的动态增长,防止空间浪费。
操作(以STL中的stack库为例)
cpp
#include<stack>
stack<int> s; //新建栈s,存储元素类型为int
s.size(); //返回栈中元素数量
s.empty(); //判断栈是否为空,空则返回true
s.push(node); //入栈操作,node入栈
s.pop(); //出栈操作
s.top(); //获取栈顶元素
链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针 链接次序实现的。
链表由一系列节点 (链表中的每一个元素称为节点)组成,节点可以再运行时动态生成,克服了数组需要预先知道数据量大小的缺点。每个节点包括两个部分:一个是存储数据元素的数据域 ,另一个时存储相邻节点地址的指针域 。
特性
根据指针域的不同,链表可分为单向链表和双向链表,单向链表中各节点只记录后继元素的地址,双向链表的节点会记录前驱、后继两个节点的地址。
相较于线性表的顺序结构,链表操作复杂。由于不必按顺序存储,链表在插入的时候可以达到 O ( 1 ) O(1) O(1)的复杂度,比线性表快得多,但是查找一个节点或者访问特定编号的节点则需要 O ( n ) O(n) O(n)的时间,而线性表和顺序表相应的时间复杂度分别是 O ( l o g n ) O(log n) O(logn)和 O ( 1 ) O(1) O(1)。
逻辑结构(双向链表)
物理结构
链表使用链式存储方式存储数据,便于插入和删除,遍历方面可以通过ST表等进行优化。竞赛中链表的插入和删除操作顺序时常考点。
哈希表
哈希表(hash table)又称散列表,是根据数据关键值直接进行访问的数据结构。也就是说,它通过把关键值有映射到表中的一个位置来存储和访问,从而加快查找的速度。这个映射函数称为哈希函数(散列函数) ,存放数据的数组称为哈希表(散列表) 。
给定表 M M M,若存在函数 f ( k e y ) f(key) f(key),对任意给定的关键字 k e y key key,带入函数后能得到包含关键字的记录在表中的地址,则称表 M M M为哈希表,函数 f ( k e y ) f(key) f(key)为哈希函数。
简单来说,哈希表是一种通过哈希函数将特定的键映射到特定值的一种数据结构,它维护者键和值之间的一一对应关系。
- 键 ( k e y ) (key) (key):又称为关键字,是要存储的数据的唯一标识符,可以是数据本身或者数据的一部分。
- 槽 ( s l o t / b u c k e t ) (slot/bucket) (slot/bucket):哈希表中用于保存数据的一个单元,即数据存放的容器。
- 哈希函数 ( h a s h f u n c t i o n ) (hash function) (hashfunction):将键 ( k e y ) (key) (key)映射到值 ( v a l u e ) (value) (value)的函数,映射出的值即存放数据的槽所在的位置。
- 哈希冲突 ( h a s h c o l l i s i o n ) (hash collision) (hashcollision):哈希函数将两个不同键映射到同一个值得情况。
举例
现有一个数组 a [ 10 ] a[10] a[10],待存入得数据有 1 、 2 、 3 、 4 、 5 1、2、3、4、5 1、2、3、4、5,若哈希函数为 f ( x ) = x f(x)=x f(x)=x,那么各数据存入为 a [ f ( x ) ] = x a[f(x)]=x a[f(x)]=x,带入得:
- a [ f ( 1 ) ] = 1 → a [ 1 ] = 1 a[f(1)]=1 \rightarrow a[1]=1 a[f(1)]=1→a[1]=1
- a [ f ( 2 ) ] = 2 → a [ 2 ] = 2 a[f(2)]=2 \rightarrow a[2]=2 a[f(2)]=2→a[2]=2
- a [ f ( 3 ) ] = 3 → a [ 3 ] = 3 a[f(3)]=3\rightarrow a[3]=3 a[f(3)]=3→a[3]=3
- a [ f ( 4 ) ] = 4 → a [ 4 ] = 4 a[f(4)]=4 \rightarrow a[4]=4 a[f(4)]=4→a[4]=4
- a [ f ( 5 ) ] = 5 → a [ 5 ] = 5 a[f(5)]=5 \rightarrow a[5]=5 a[f(5)]=5→a[5]=5
由此便得到了一个简单得哈希数组{ − , 1 , 2 , 3 , 4 , 5 , − , − , − , − -,1,2,3,4,5,-,-,-,- −,1,2,3,4,5,−,−,−,−}。
再来一各数组 a [ 11 ] a[11] a[11],带存入的数据有 2 、 6 、 10 、 17 2、6、10、17 2、6、10、17,若哈希函数为 f ( x ) = x f(x)=x f(x)=x m o d mod mod 11 11 11,那么: f ( 2 ) = 2 , f ( 6 ) = 6 , f ( 10 ) = 10 , f ( 17 ) = 6 f(2)=2,f(6)=6,f(10)=10,f(17)=6 f(2)=2,f(6)=6,f(10)=10,f(17)=6。
键 6 6 6和 17 17 17通过哈希函数映射出的值均为 6 6 6,发生了冲突,应考虑更换哈希函数或解决冲突。
范例精讲
例1 向一个栈顶指针为 h s hs hs的链式栈中插入一个指针 s s s指向的节点时,应执行( )。
A. h s hs hs-> n e x t = s ; next=s; next=s;
B. s s s-> n e x t = h s ; h s = s ; next=hs;hs=s; next=hs;hs=s;
C. s s s-> n e x t = h s next=hs next=hs-> n e x t ; h s next;hs next;hs-> n e x t = s ; next=s; next=s;
D. s s s-> n e x t = h s ; h s = h s next=hs;hs=hs next=hs;hs=hs-> n e x t ; next; next;
【正确答案】B
解析 :为栈顶元素 n e x t next next指针赋值就是入栈操作,入栈后应记得更新栈顶指针。
例2 双向链表中有两个指针域 l l i n k llink llink和 r l i n k rlink rlink,分别指向前驱与后继。设 p p p指向链表中的一个节点, q q q指向一待插入节点,现要求在 p p p前插入 q q q,则正确的插入为( )。
A. p p p-> l l i n k = q ; q llink=q;q llink=q;q-> r l i n k = p ; p rlink=p;p rlink=p;p-> l i n k link link-> r l i n k = q ; q rlink=q;q rlink=q;q-> l l i n k = p llink=p llink=p-> l l i n k ; llink; llink;
B. q q q-> l l i n k = p llink=p llink=p-> l l i n k ; p llink;p llink;p-> l l i n k llink llink-> r l i n k = q ; q rlink=q;q rlink=q;q-> r l i n k = p ; p rlink=p;p rlink=p;p-> l l i n k = q llink=q llink=q-> r l i n k ; rlink; rlink;
C. q q q-> r l i n k = p ; p rlink=p;p rlink=p;p-> r l i n k = q ; p rlink=q;p rlink=q;p-> l l i n k llink llink-> r l i n k = q ; q rlink=q;q rlink=q;q-> r l i n k = p ; rlink=p; rlink=p;
D. p p p-> l l i n k llink llink-> r l i n k = q ; q rlink=q;q rlink=q;q-> r l i n k = p ; q rlink=p;q rlink=p;q-> l l i n k = p llink=p llink=p-> l l i n k ; p llink;p llink;p-> l l i n k = q ; llink=q; llink=q;
【正确答案】D
解析 :插入新节点时,需要注意改变指针指向时不要弄丢节点( p p p-> l l i n k llink llink)。题目最终要实现的效果如下:
第一步: p p p-> l l i n k llink llink-> r l i n k = q ; rlink=q; rlink=q;
第二步: q q q-> r l i n k = p rlink=p rlink=p
第三步: q q q-> l l i n k = p llink=p llink=p-> l l i n k llink llink
第四步: p p p-> l l i n k = q llink=q llink=q
赛题训练
- 今有一空栈 S S S,对下列待进栈的数据元素序列 a 、 b 、 c 、 d 、 e 、 f a、b、c、d、e、f a、b、c、d、e、f依次进行进栈、进栈、出栈、进栈、进栈、出栈的操作,则此操作完成后,栈底元素为( )。
A. b b b B. a a a C. d d d D. c c c - 将 ( 2 , 7 , 10 , 18 ) (2,7,10,18) (2,7,10,18)分别存储到某个地址区间为 0 0 0~ 10 10 10的哈希表中,如果哈希函数 h ( x ) = ( h(x)=( h(x)=( ) ) ),将不会产生冲突,其中 a a a m o d mod mod b b b表示 a a a除以 b b b的余数。
A. x 2 x^2 x2 m o d mod mod 11 11 11 B. 2 x 2x 2x m o d mod mod 11 11 11 C. x x x m o d mod mod 11 11 11 D. x / 2 x/2 x/2 m o d mod mod 11 11 11(除法下取整) - 链表不具有的特点是( )。
A.可随机访问任一元素。
B.不必事先估计存储空间。
C.插入和删除不需要移动元素。
D.所需空间与线性表长度成正比。 - 下图中所使用的数据结构是( )。
A.栈 B.队列 C.二叉树 D.哈希表