Download - 2 线性表 (2)

Transcript
Page 1: 2  线性表 (2)

数据结构 --- 线性表

2 2 线性表线性表(2)(2)

Page 2: 2  线性表 (2)

数据结构 --- 线性表

1.了解线性结构的特点2.掌握顺序表的定义、查找、插入和删除 3.掌握链表的定义、查找、插入和删除 4.能够从时间和空间复杂度的角度比较两种

存储结构的不同特点及其适用场合5.应用线性结构解决基本问题6.了解 STL中的 vector,list的基本使

用方法

1.了解线性结构的特点2.掌握顺序表的定义、查找、插入和删除 3.掌握链表的定义、查找、插入和删除 4.能够从时间和空间复杂度的角度比较两种

存储结构的不同特点及其适用场合5.应用线性结构解决基本问题6.了解 STL中的 vector,list的基本使

用方法

教学目标

Page 3: 2  线性表 (2)

数据结构 --- 线性表

2.1 线性表的定义

2.2 线性表的顺序表示和实现

2.3 线性表的链式表示和实现

2.4 线性表与 STL(标准模板库)

2.1 线性表的定义

2.2 线性表的顺序表示和实现

2.3 线性表的链式表示和实现

2.4 线性表与 STL(标准模板库)

教学内容

Page 4: 2  线性表 (2)

数据结构 --- 线性表

2.3 线性表的链式表示和实现

Page 5: 2  线性表 (2)

数据结构 --- 线性表

一、单链表二、结点和单链表的 C++ 语言描述三、线性表的操作在单链表中的实现四、其它形式的链表五、编程实现带头结点的单链表

Page 6: 2  线性表 (2)

数据结构 --- 线性表

用一组地址任意的存储单元存放线性表中的数据元素。

一、单链表

以元素 ( 数据元素的存储 )

+ 指针 ( 指示后继元素存储位置 )

= 结点以“结点的序列”表示线性表 称作链表

Page 7: 2  线性表 (2)

数据结构 --- 线性表

以线性表中第一个数据元素 的存储地址作为线性表的地址,称作线性表的头指针。

1a

头结点 a1 a2 … ... an ^

头指针头指针

有时为了操作方便,在第一个结点之前虚加一个“头结点”,以指向头结点的指针为链表的头指针。 ( 这样,头指针永不为空 )

空指针线性表为空表时,头结点的指针域为空

Page 8: 2  线性表 (2)

数据结构 --- 线性表

struct LinkList{ LNode *head; // 头指针 ( 带头结点 ) //LNode *tail , *cur;// 尾指针 , 当前指针 //int length; // 链表长度 bool Init(); // 初始化 void Clear(); // 清除线性表 void Create(int n); // 建立含 n 个结点的单链表 int Locate(ElemType e); // 查找 bool InsertBefore(int i, ElemType e);// 插入元素 bool Delete(int i); // 删除元素 void Traverse(); // 遍历,并输出内容 bool Empty(); // 判断空表};

二、 线性表链式映像的 C++ 语言描述struct LNode { ElemType data; // 数据域 Lnode *next; // 指针域} ;

Page 9: 2  线性表 (2)

数据结构 --- 线性表

ai-1

InsertBefore(i, e) 的实现: 有序对 <ai-1, ai> 改变为 <ai-1, e> 和 <e, ai>

e

aiai-1

因此,在单链表中第 i 个结点之前进行插入的基本操作为 : 找到线性表中第 i-1个结点,然后修改其指向后继的指针。

三、线性表的操作在单链表中的实现

Page 10: 2  线性表 (2)

数据结构 --- 线性表

void InsertBefore(int i, int e) {

// 本算法在链表第 i 个结点之前插入新的元素 e

} // InsertBefore算法的时间复杂度为 : O(n)

p = head;

while (p && i>1) { p=p->next; i--; }//找第 i-1个结点if (p==NULL || i <1) return; // i 大于表长或者小于 1 s = new LNode; // 生成新结点s->data = e;

s->next = p->next; p->next = s; // 插入

Page 11: 2  线性表 (2)

数据结构 --- 线性表

Page 12: 2  线性表 (2)

数据结构 --- 线性表

核心代码:s = new LNode; // 生成新结点s->data = e;

s->next = p->next; p->next = s; // 插入

e

ai-1 aiai-1

s

p

Page 13: 2  线性表 (2)

数据结构 --- 线性表

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

ai-1 ai ai+1ai-1

q = p->next; p->next = q->next;

e = q->data; delete q;p q

Delete (i, &e) 的实现 :

有序对 <ai-1, ai> 和 <ai, ai+1> 改变为 <ai-1,

ai+1>

Page 14: 2  线性表 (2)

数据结构 --- 线性表

void Delete (int i, int &e) {

// 删除单链表中第 i 个结点

} // Delete算法的时间复杂度为 : O(n)

p = head; j = 0;while (p->next && j < i-1) { p = p->next; ++j; } // 寻找第 i 个结点,并令 p 指向其前趋if (!(p->next) || j > i-1) return; // 删除位置不合理

q = p->next; p->next = q->next; // 删除并释放结点e = q->data; delete q;

Page 15: 2  线性表 (2)

数据结构 --- 线性表

Page 16: 2  线性表 (2)

数据结构 --- 线性表

Clear( ) 的实现 :

void Clear( ) {

// 将单链表重新置为一个空表 while (head->next) {

p=head->next;

head->next=p->next;

}

} // Clear

delete p;

算法时间复杂度:O(n)

Page 17: 2  线性表 (2)

数据结构 --- 线性表

如何从线性表得到单链表?

链表是一个动态的结构,它不需要预先分配空间,因此生成链表的过程是一个结点“逐个插入”的过程。

Page 18: 2  线性表 (2)

数据结构 --- 线性表

例如:逆位序输入 n 个数据元素的值, 建立带头结点的单链表。操作步骤:一、建立一个“空表”;二、输入数据元素 an , 建立结点并插入;三、输入数据元素 an-1 , 建立结点并插入;

an

an

an-1

四、依次类推,直至输入 a1 为止。

Page 19: 2  线性表 (2)

数据结构 --- 线性表

void Create(int n) { // 逆序输入 n 个数据元素,建立带头结点的单链表

} // Create算法的时间复杂度为 : O(n)

head = new LNode;head->next =NULL; // 先建立一个带头结点的单链表

for (i = n; i > 0; i--) { p = new LNode; cin>>p->data; // 输入元素值 p->next = head->next; head->next = p; // 插入}

Page 20: 2  线性表 (2)

数据结构 --- 线性表

回顾 前面例 2.1, 2.2, 2.3 三个例子的算法,看一下当线性表分别以顺序表和链表实现时,它们的时间复杂度为多少?

2-1 两集合用线性表 LA 和 LB 表示,求新集合 A = A B∪ 。 void union(List &La, List Lb)

2-2 非纯集合 B 净化为纯集合 A 。void purge(List &La, List Lb)

2-3 void MergeList(List La, List Lb, List &Lc)

Page 21: 2  线性表 (2)

数据结构 --- 线性表

void union(List &La, List Lb) { La_len = ListLength(La); Lb_len =ListLength(Lb); for (i = 1; i <= Lb_len; i++) { GetElem(Lb, i, e); if (!Locate(La, e) InsertBefore (La, ++La_len, e); }//for} // union

控制结构:基本操作:

for 循环GetElem, Locate 和 InsertBefore

当以顺序映像实现抽象数据类型线性表时为 : O( LenA×LenB )

当以链式映像实现抽象数据类型线性表时为 : O( LenA×LenB )

例 2-1

算法时间复杂度算法时间复杂度

Page 22: 2  线性表 (2)

数据结构 --- 线性表

void purge(List &La, List Lb) { // Lb 元素有序 InitList(LA); La_len = ListLength(La); Lb_len =ListLength(Lb); for (i = 1; i <= Lb_len; i++) { GetElem(Lb, i, e); if (ListEmpty(La) || en!=e) { InsertBefore(La, ++La_len, e); en = e; } }//for} // purge

控制结构:基本操作:

for 循环GetElem 和 InsertBefore

当以顺序映像实现抽象数据类型线性表时为 :O(LenB)当以链式映像实现抽象数据类型线性表时为 :O(LenB2)

例 2-2

算法时间复杂度算法时间复杂度

Page 23: 2  线性表 (2)

数据结构 --- 线性表

void MergeList(List La, List Lb, List &Lc) { InitList(Lc); i = j = 1; k = 0; La_len = ListLength(La); Lb_len = ListLength(Lb); while ((i <= La_len) && (j <= Lb_len)) { GetElem(La, i, ai); GetElem(Lb, j, bj); if (ai <= bj) { InsertBefore (Lc, ++k, ai); ++i; } else { InsertBefore (Lc, ++k, bj); ++j; } } … …

控制结构:基本操作:

三个并列的 while 循环GetElem, InsertBefore

当以顺序映像实现抽象数据类型线性表时为 : O( LenA+LenB)当以链式映像实现抽象数据类型线性表时为 : O( (LenA+LenB)2 )

例 2-3

算法时间复杂度算法时间复杂度

Page 24: 2  线性表 (2)

数据结构 --- 线性表

用上述定义的单链表实现线性表的操作时,存在的问题:

改进链表的设置:

1.单链表的表长是一个隐含的值;2.在单链表的最后一个元素之后插入元素时,

需遍历整个链表;3.在链表中,元素的“位序”概念淡化,结点

的“位置”概念加强。

1.增加“表长”、“表尾指针” 和 “当前位置的指针” 三个数据域;

2.将基本操作中的“位序 i ” 改变为“指针 p ” 。

Page 25: 2  线性表 (2)

数据结构 --- 线性表

void InsAfter(int e ) { // 将数据元素 e 插入在链表当前指针所指结点之后

} // InsAfter

s=new LNode; s->data=e; s->next = curr->next; curr->next = s; if (tail = curr) tail = s; curr = s; // 注意

Page 26: 2  线性表 (2)

数据结构 --- 线性表

void DelAfter(int & e ) {

// 删除当前指针所指结点之后的结点 , 操作失败时 e=NULL

} //DelAfter

q = curr->next;curr->next = q->next;if (tail = q) tail = curr;e=q->data; delete q;

Page 27: 2  线性表 (2)

数据结构 --- 线性表

void MergeList(LinkList &Lc, LinkList &La,LinkList &Lb) { Lc.Init(); // 初始化 La.curr = La.head->next; Lb.curr = Lb.head->next; // 当前指针指向头结点 while (! La.Empty( ) || !Lb.Empty( )) { // La或 Lb 非空

a=La.GetCurElem(); b=Lb.GetCurElem(); If(a<b)

La.DelAfter( La, e ) ; // 当前指针后移else

Lb.DelAfter( Lb, e ) ;Lc.InsertAfter(e);

} La.destory(); Lb. destory(); // 销毁链表 La 和 Lb} // MergeList

例:归并有序表 La 和 Lb , 生成新有序表 Lc 之后销毁 La 和 Lb

O( LenA+LenB)算法时间复杂度 :算法时间复杂度 :

Page 28: 2  线性表 (2)

数据结构 --- 线性表

1. 双向链表四、其它形式的链表

struct DuLNode {

ElemType data; // 数据域DuLNode *prior; // 指向前驱的指针DuLNode *next; // 指向后继的指针

};操作特点:“查询”和单链表相同。“插入”和“删除”需要同时修改两个方向上的指针。

Page 29: 2  线性表 (2)

数据结构 --- 线性表

最后一个结点的指针域的指针又指回第一个结点的链表

a1 a2 … ... an

2. 循环链表

和单链表的差别仅在于,判别链表中最后一个结点的条件不再是“后继是否为空”,而是“后继是否为头结点”。

Page 30: 2  线性表 (2)

数据结构 --- 线性表

ai-1 ai

e

s->next = p->next; p->next = s;

s->next->prior = s; s->prior = p;

p

s

ai-1 ai

双向链表插入

Page 31: 2  线性表 (2)

数据结构 --- 线性表

ai-1

双向链表删除

ai ai+1

p->next = p->next->next;

p->next->prior = p;

p

ai-1

为简单起见,这里没有释放被删除节点的空间

Page 32: 2  线性表 (2)

数据结构 --- 线性表

五、编程实现带头结点的单链表struct LinkList{

LNode *head; // 头结点int size; // 链表长度

LinkList(); // 构造函数 , 初始化单链表void create(int a[],int n); // 根据数组 A 中的 n 个元素,建立单链表void clear(); // 释放除头结点外的其他结点bool insert(int i, int e); // 在单链表的第 i个位置前插入 ebool erase(int i); // 在单链表中删除第 i个元素 ,并用 e返回其值void traverse(); // 遍历链表,并输出依序内容LNode* locate(int e); // 查找首个值等于 e的位置 ( 指针 ),否则返回 NULL bool getElem(int i, int &e) // 在单链表中取得第 i个元素void reverse(); // 逆置链表

};

struct LNode{int data;LNode *next;

};

Page 33: 2  线性表 (2)

数据结构 --- 线性表

LinkList() { // 构造函数 , 初始化单链表head=new LNode; // 生成头结点head->next=NULL;size=0;

}

void clear(){ // 释放除头结点外的其他结点LNode *p;while(head->next!=NULL){

p=head->next;head->next=p->next;delete p;

}size=0;

}

Page 34: 2  线性表 (2)

数据结构 --- 线性表

bool erase(int i) { // 在单链表中删除第 i个元素if(i<1 || i>size) return false;

LNode *p=head;while(p->next!=NULL && i>1){

p=p->next;i--;

}

LNode *q=p->next;p->next=q->next;delete q;size--;return true;

}

Page 35: 2  线性表 (2)

数据结构 --- 线性表

bool getElem(int i, int &e){ // 在单链表中取得第 i个元素

if(i<1 || i>size) return false;

LNode *p=head->next;//指向第一个节点

while(p!=NULL && i>1){i--;p=p->next;

}

e=p->data;return true;

}

Page 36: 2  线性表 (2)

数据结构 --- 线性表

int main(){LinkList lst;int a[6]={11,12,13,14,15,16};lst.create(a,6); lst.traverse();lst.erase(3); lst.traverse();lst.insert(6,99); lst.traverse();lst.reverse(); lst.traverse();int e; lst.getElem(1,e); cout<<e<<endl;

LNode *p=lst.locate(13);if(p!=NULL) cout<<p->explain<<endl;else cout<<"No found!"<<endl;

lst.clear();lst.create(a,3); lst.traverse();return 0;

}

单链表使用举例单链表使用举例

Page 37: 2  线性表 (2)

数据结构 --- 线性表

小结1. 熟练掌握链表中的头结点、头指针和首元结

点的区别。2. 熟练掌握链表的查找、插入和删除算法3. 能够从时间和空间复杂度的角度比较两种存

储结构的不同特点及其适用场合

1. 熟练掌握链表中的头结点、头指针和首元结点的区别。

2. 熟练掌握链表的查找、插入和删除算法3. 能够从时间和空间复杂度的角度比较两种存

储结构的不同特点及其适用场合

Page 38: 2  线性表 (2)

数据结构 --- 线性表

课后建议:

HLoj 8801-8808,8812-8824 HDoj 1002, 1715,1753 (大整数加

法)


Top Related