1 / 38

2 线性表 (2)

2 线性表 (2). 教学目标. 1. 了解线性结构的特点 2. 掌握顺序表的定义、查找、插入和删除 3. 掌握链表的定义、查找、插入和删除 4. 能够从时间和空间复杂度的角度比较两种存储结构的不同特点及其适用场合 5. 应用线性结构解决基本问题 6. 了解 STL 中的 vector,list 的基本使用方法. 教学内容. 2.1 线性表的定义 2.2 线性表的顺序表示和实现 2.3 线性表的链式表示和实现 2.4 线性表与 STL (标准模板库). 2.3 线性表的链式表示和实现. 一、单链表 二、结点和单链表的 C++ 语言描述

doria
Download Presentation

2 线性表 (2)

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 2 线性表(2)

  2. 教学目标 1.了解线性结构的特点 2.掌握顺序表的定义、查找、插入和删除 3.掌握链表的定义、查找、插入和删除 4.能够从时间和空间复杂度的角度比较两种存储结构的不同特点及其适用场合 5.应用线性结构解决基本问题 6.了解STL中的vector,list的基本使用方法

  3. 教学内容 2.1 线性表的定义 2.2 线性表的顺序表示和实现 2.3 线性表的链式表示和实现 2.4 线性表与STL(标准模板库)

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

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

  6. 一、单链表 用一组地址任意的存储单元存放线性表中的数据元素。 以元素(数据元素的存储) + 指针(指示后继元素存储位置) = 结点 以“结点的序列”表示线性表  称作链表

  7. a1 a2 … ... an ^ 以线性表中第一个数据元素的存储地址作为线性表的地址,称作线性表的头指针。 头指针 线性表为空表时, 头结点的指针域为空 头指针 空指针 头结点  有时为了操作方便,在第一个结点之前虚加一个“头结点”,以指向头结点的指针为链表的头指针。(这样,头指针永不为空)

  8. 二、线性表链式映像的 C++ 语言描述 struct LNode { ElemType data; // 数据域 Lnode*next; // 指针域 } ; 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(); // 判断空表 };

  9. ai-1 ai ai-1 e 三、线性表的操作在单链表中的实现 InsertBefore(i, e)的实现: 有序对 <ai-1, ai> 改变为 <ai-1, e> 和<e, ai> 因此,在单链表中第 i 个结点之前进行插入的基本操作为:找到线性表中第i-1个结点,然后修改其指向后继的指针。

  10. void InsertBefore(int i, int e) { // 本算法在链表第i 个结点之前插入新的元素 e } // InsertBefore 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; // 插入 O(n) 算法的时间复杂度为:

  11. ai-1 ai ai-1 e 核心代码: s = new LNode; // 生成新结点 s->data = e; s->next = p->next; p->next = s; // 插入 p s

  12. ai-1 ai-1 ai ai+1 Delete (i, &e)的实现: 有序对<ai-1, ai> 和 <ai, ai+1>改变为 <ai-1, ai+1> 在单链表中删除第i 个结点的基本操作为:找到线性表中第i-1个结点,修改其指向后继的指针。核心代码如下: q = p->next; p->next = q->next; e = q->data; delete q; p q

  13. void Delete (int i, int &e) { // 删除单链表中第 i 个结点 } // Delete 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; 算法的时间复杂度为: O(n)

  14. Clear( ) 的实现: void Clear( ) { // 将单链表重新置为一个空表 while (head->next) { p=head->next; head->next=p->next; } } // Clear delete p; 算法时间复杂度: O(n)

  15. 如何从线性表得到单链表? 链表是一个动态的结构,它不需要预先分配空间,因此生成链表的过程是一个结点“逐个插入”的过程。

  16. 例如:逆位序输入 n 个数据元素的值, 建立带头结点的单链表。 操作步骤: 一、建立一个“空表”; 二、输入数据元素an, 建立结点并插入; an 三、输入数据元素an-1, 建立结点并插入; an an-1 四、依次类推,直至输入a1为止。

  17. void Create(int n) { // 逆序输入 n 个数据元素,建立带头结点的单链表 } // Create 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; // 插入 } O(n) 算法的时间复杂度为:

  18. 回顾 前面例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)

  19. 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 例2-1 算法时间复杂度 控制结构: 基本操作: for 循环 GetElem, Locate 和 InsertBefore 当以顺序映像实现抽象数据类型线性表时为: O( LenA×LenB ) 当以链式映像实现抽象数据类型线性表时为: O( LenA×LenB )

  20. 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 例2-2 算法时间复杂度 控制结构: 基本操作: for 循环 GetElem 和 InsertBefore 当以链式映像实现抽象数据类型线性表时为:O(LenB2) 当以顺序映像实现抽象数据类型线性表时为:O(LenB)

  21. 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; } } … … 例2-3 算法时间复杂度 控制结构: 基本操作: 三个并列的while循环 GetElem, InsertBefore 当以链式映像实现抽象数据类型线性表时为: O( (LenA+LenB)2 ) 当以顺序映像实现抽象数据类型线性表时为: O( LenA+LenB)

  22. 用上述定义的单链表实现线性表的操作时, 存在的问题: 单链表的表长是一个隐含的值; 在单链表的最后一个元素之后插入元素时,需遍历整个链表; 在链表中,元素的“位序”概念淡化,结点的“位置”概念加强。 改进链表的设置: 增加“表长”、“表尾指针” 和 “当前位置的指针” 三个数据域; 将基本操作中的“位序 i ”改变为“指针 p ”。

  23. 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; // 注意

  24. void DelAfter(int & e ) { // 删除当前指针所指结点之后的结点, 操作失败时e=NULL } //DelAfter q = curr->next; curr->next = q->next; if (tail = q) tail = curr; e=q->data; delete q;

  25. 例:归并有序表 La 和 Lb ,生成新有序表 Lc之后销毁La 和 Lb 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 算法时间复杂度: O( LenA+LenB)

  26. 四、其它形式的链表 1. 双向链表 struct DuLNode { ElemType data;// 数据域 DuLNode *prior;// 指向前驱的指针 DuLNode *next;// 指向后继的指针 }; 操作特点: “查询”和单链表相同。 “插入”和“删除”需要同时修改两个方向上的指针。

  27. 2. 循环链表 最后一个结点的指针域的指针又指回第一个结点的链表 a1 a2 … ... an 和单链表的差别仅在于,判别链表中最后一个结点的条件不再是“后继是否为空”,而是“后继是否为头结点”。

  28. ai-1 ai e ai-1 ai p 双向链表插入 s s->next = p->next; p->next = s; s->next->prior = s; s->prior = p;

  29. ai-1 ai ai+1 ai-1 p 双向链表删除 p->next = p->next->next; p->next->prior = p; 为简单起见,这里没有释放被删除节点的空间

  30. 五、编程实现带头结点的单链表 struct LinkList{ LNode *head; // 头结点 int size; // 链表长度 LinkList(); // 构造函数, 初始化单链表 void create(int a[],int n); // 根据数组A 中的n 个元素,建立单链表 void clear(); // 释放除头结点外的其他结点 bool insert(int i, int e);// 在单链表的第i个位置前插入e bool 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; };

  31. 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; }

  32. 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; }

  33. 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; }

  34. 单链表使用举例 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; }

  35. 小结 熟练掌握链表中的头结点、头指针和首元结点的区别。 熟练掌握链表的查找、插入和删除算法 能够从时间和空间复杂度的角度比较两种存储结构的不同特点及其适用场合

  36. 课后建议: HLoj 8801-8808,8812-8824 HDoj 1002, 1715,1753(大整数加法)

More Related