1 / 42

数据结构(二 )

数据结构(二 ). 常宝宝 北京大学计算机科学与技术系 chbb@pku.edu.cn. 内容提要. 线性表的定义 线性表的实现 — 顺序存储结构 线性表的实现 — 链式存储结构 单向链表 双向链表. 线性表. 线性表 n 个结点(数据元素)的有限序列。 始结点唯一,即存在一个结点没有前趋结点。 终结点唯一,即存在一个结点没有后继结点。 除始结点外,其它结点均只有一个前趋结点。 除终结点外,其它结点均只有一个后继结点。 线性表举例 简单数据元素 (6, 17, 28, 50, 92, 188) ( A, B, D, E, C) 复杂数据元素

Télécharger la présentation

数据结构(二 )

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. 数据结构(二) 常宝宝 北京大学计算机科学与技术系 chbb@pku.edu.cn

  2. 内容提要 • 线性表的定义 • 线性表的实现 — 顺序存储结构 • 线性表的实现 — 链式存储结构 • 单向链表 • 双向链表

  3. 线性表 • 线性表 • n个结点(数据元素)的有限序列。 • 始结点唯一,即存在一个结点没有前趋结点。 • 终结点唯一,即存在一个结点没有后继结点。 • 除始结点外,其它结点均只有一个前趋结点。 • 除终结点外,其它结点均只有一个后继结点。 • 线性表举例 • 简单数据元素 (6, 17, 28, 50, 92, 188) (A, B, D, E, C) • 复杂数据元素 学生成绩登记表

  4. 线性表 • 线性表中的各个数据元素具有相同(或相容)的类型。 • 线性表中的元素个数称为线性表的长度。 • 长度为0的线性表称为空表。 • 线性表可视为容纳某类对象的容器。 • 线性表中每个元素有一个表明其位置的编号,始结点编号为0,始结点的直接后继结点编号为1,…,终结点的编号为n-1。(线性表长度为n)

  5. 线性表 • 和线性表有关的操作: • 构造一个空表 • 判断线性表是否空表 • 判断线性表是否已满 • 返回线性表的长度 • 将一个线性表清空 • 在线性表的第i个位置插入一个元素 • 删除线性表中第i个元素 • 读取线性表中第i个元素 • 把线性表中的第i个元素替换为另外一个元素 • 顺序遍历线性表中的元素,并对每个元素进行特定的处理

  6. 抽象数据类型定义 template <typename list_entry>class List {public: enum error_code { success, range_error, overflow, underflow};protected: ...public: //操作List(); List(const List<list_entry>& copy); ~List(); int size() const; bool full() const; bool empty() const; void clear(); error_code retrieve(int i, list_entry& x) const; error_code replace(int i, const list_entry& x); error_code remove(int i, list_entry& x); error_code insert(int i, const list_entry& x); void traverse( void (*visit)(list_entry&));};

  7. 线性表操作 List<list_entry>::List();// PRECONDITION:// POSTCONDITION: 建立了一个空表 void List<list_entry>::clear();// PRECONDITION:// POSTCONDITION: 表中所有元素被删除,变成空表 int List<list_entry>::size() const;// PRECONDITION:// POSTCONDITION: // REMARKS:表中元素个数被返回 bool List<list_entry>::empty() const;// PRECONDITION:// POSTCONDITION: // REMARKS:若线性表是空表,返回true,否则返回flase

  8. 线性表操作 error_code List<list_entry>::insert(int i, const list_entry& x);// PRECONDITION: 线性表未满, 0≤i≤n// POSTCONDITION: 线性表的长度增加1,x成为表中的第i个元素,表中原来第i个元// 素及其后继元素位置编号加1// REMARKS:若操作成功,返回success,否则返回错误代码// overflow --- 上溢// range_error --- 插入位置无效 void List<list_entry>::traverse( void (*visit)(list_entry&));// PRECONDITION: // POSTCONDITION: 对线性表中每个元素进行visit规定的操作// REMARKS:

  9. 线性表的使用 ...void add(int& x ) { x++; } int main() { List<int> intList; //创建一个元素类型是整数的线性表int x; intList.insert(0, 10); //在线性表中插入一个整数10intList.insert(1, 12); //在线性表中插入一个整数12 if ( intList.empty() ) //判断线性表是否是空表cout << “空表” << endl; else cout << “非空表” << endl; intList.traverse(add); //遍历线性表元素,并给每个元素加1intList.remove(1,x); //删除线性表中第1个元素cout << intList.size() << endl; //显示线性表中的元素个数 ...}

  10. 线性表的实现 —顺序存储 • 顺序映象以存储位置先后表示元素间的前趋和后继关系。 • 用一组地址连续的存储单元依次存放线性表中的数据元素。 a0a1…ai-1ai…an-1 线性表的起始地址 称作线性表的基地址

  11. 线性表的实现 —顺序存储 • 若每个元素占用 l 个存储单元,用其中第一个单元的地址作为元素的存储位置。则元素 ai 的存储位置可以表示为LOC(ai)。 • 线性表中第 i+1 个元素和第 i个元素的存储位置间满足下列关系:LOC(ai+1) = LOC(ai) + l • 线性表中第 i个元素的存储位置为:LOC(ai) = LOC(a0) + i * l其中LOC(a0)是线性表的基地址。 • C++中,数组元素占据连续存储单元,可用来实现顺序存储的线性表。

  12. 线性表的实现 —顺序存储 template <typename list_entry, int max_list=100 >class List {public: enum error_code { success, range_error, overflow, underflow};protected:int count; //线性表中元素个数list_entry entry[max_list]; //线性表存储空间public: //操作List(); List(const List<list_entry>& copy); ~List(); int size() const; bool full() const; bool empty() const; void clear(); error_code retrieve(int i, list_entry& x) const; error_code replace(int i, const list_entry& x); error_code remove(int i, list_entry& x); error_code insert(int i, const list_entry& x); void traverse( void (*visit)(list_entry&));};

  13. 线性表的实现 —顺序存储 template <typename list_entry, int max_list >List<list_entry, max_list>::List():count(0) {} template <typename list_entry, int max_list >int List<list_entry, max_list>::size() const { return count;} template <typename list_entry, int max_list >bool List<list_entry, max_list>::full() const { if ( count == max_list ) return true; return false;} template <typename list_entry, int max_list >error_code List<list_entry, max_list>::retrieve(int i, list_entry& x) const { if ( i<0 || i>count-1 ) return range_error; x = entry[i]; return success;}

  14. 线性表的实现 —顺序存储 template <typename list_entry, int max_list >error_code List<list_entry, max_list>::insert(int i, list_entry& x) { if ( full() ) return overflow; if ( i<0 || i>count) return range_error; for ( int j=count-1; j>=i; j--) entry[j+1] = entry[j]; entry[i] = x; count++; return success;} • xx template <typename list_entry, int max_list >void List<list_entry, max_list>::traverse( void (*visit)(list_entry&)); { for ( int i=0; i<count; i++) (*visit)(entry[i]);}

  15. 线性表的实现 —顺序存储 • 元素插入操作的时间复杂度分析 • 假设在第i个位置插入元素的概率为pi,则在长度为n 的线性表中插入一个元素所需移动元素次数的期望值为: • 假定在线性表中任何一个位置上进行插入的概率都是相等的,则移动元素的期望值为: • 在线性表中插入一个元素平均需要移动一半元素,时间复杂度是线性阶,即O(n)。

  16. 线性表的实现 —顺序存储 • 在顺序实现中:☆ insert 和 remove 的时间复杂度是O(n)。☆ List、clear、empty、full、size、replace和retrieve的时间复杂度是O(c)。 • 优点: ☆ 随机存取( retrieve ) • 缺点 ☆ 插入( insert )、删除( remove )需移动大量元素 ☆ 需要预先估计所需最大空间 ☆ 表的容量不能动态扩充 • 思考:如何实现容量可动态增长的顺序存储的线性表?

  17. 线性表的实现 —单链表 • 用一组地址任意的存储单元存放线性表中的数据元素。用附加的指针指示元素的前趋和后继关系。 • 为了表示数据元素ai 与其直接后继元素ai+1之间的逻辑关系,对数据元素ai 而言,除存储其本身的信息外,还需要存储指示其后继的指针(地址)信息。这两部分合起来通常称为结点。结点 = 元素 + 指针 • n个结点通过指针链接形成一个链表,即为线性表的链式存储结构。由于每个结点中包含一个指针域,所以称为单链表。

  18. 空指针 head a0 a1 … ... an^ 线性表的实现 —单链表 • 整个链表的存取必须从头指针开始进行,头指针指示链表中的第一个结点的存储位置。 • 最后一个元素由于没有后继结点,最后一个结点的指针应为空(NULL)。 • 在单链表中,数据元素之间的逻辑关系是由结点中的指针指示的,逻辑上相邻的两个元素,其存储的物理位置不必紧邻。 • 在线性表的顺序存储结构中,逻辑上相邻的元素物理位置紧邻,因而任何一个元素的存储位置都可以从线性表的起始位置计算得到,因而可实现随机存取。

  19. 线性表的实现 —单链表 • 在单链表中,任何两个元素的存储位置之间没有固定联系。每个元素的存储位置只能通过读取其前趋结点的指针域得到。 • 在单链表中,读取第i个数据元素必须从头指针出发寻找,单链表是非随机存取的存储结构。

  20. 线性表的实现 —单链表 • 在C++中定义单链表结点 • 对于线性表的客户程序而言,结点结构应该是不可见的,应把结点结构定义成链表类的私有嵌套结构。 template <typename list_entry>struct node { list_entry entry; //数据域node *next; //指针域node():next(0) {} node(const list_entry &le, node* link= NULL):entry(le), next(link) {}};

  21. 线性表的实现 —单链表 template <typename list_entry >class List {public: enum error_code { success, range_error, overflow, underflow};protected: struct node { list_entry entry; //数据域node *next; //指针域node():next(0) {} node(const list_entry &le, node* link= NULL):entry(le), next(link) {} }; int count; //线性表中元素个数 node *head; //链表头指针node *set_position(int i) const;public: //操作List(); ... void traverse( void (*visit)(list_entry&));};

  22. 线性表的实现 —单链表 • 为了实现单链表的其它操作,定义一个支持函数set_position,该函数的参数为元素的编号i,函数的功能是返回指向第i个元素的结点指针。 • set_position 是保护成员函数,只能用来实现类的其它成员函数,对单链表的客户是不可见的,因为它返回指向某个结点的指针,如果定义成公有成员,客户可以凭借该指针,直接修改结点内容,这是不安全的。 • 单链表是非随机存储结构,定位第i个结点,须从头指针开始遍历,直到找到指定的结点。 template <typename list_entry>List<list_entry>::node* List<list_entry>::set_position(int i) const { List<list_entry>::node *q = head; for (int j=0; j<i; j++) q = q->next; return q;}

  23. 线性表的实现 —单链表 template <typename list_entry >List<list_entry>::List():count(0),head(NULL) {} template <typename list_entry >int List<list_entry>::size() const { return count;} template <typename list_entry >bool List<list_entry >::empty() const { if ( count == 0 ) return true; return false;}

  24. ① 线性表的实现 —单链表 • 在单链表中插入无需大量移动元素。

  25. 线性表的实现 —单链表 template <typename list_entry >List<list_entry>::error_code List<list_entry >::insert(int i, const list_entry& x) { if ( i < 0 || i > count ) return range_error; node *newnode, *previous, *following; if ( i > 0 ) { previous = set_position( i-1); following = previous->next; } else following = head; newnode = new node(x,following); if ( newnode == 0 ) return overflow; if ( i == 0 ) head = newnode; else previous->next = newnode; count++; return success;}

  26. 线性表的实现 —单链表 • 在单链表中删除元素无需移动大量元素

  27. 线性表的实现 —单链表 template <typename list_entry>List<list_entry>::error_code List<list_entry>::remove(int i, list_entry& x) { if ( i < 0 || i > count ) return range_error; node* previous, *current; if ( i != 0 ) { previous = set_position( i-1 ); current = previous->next; previous->next = current->next; } else { current = head; head = head->next; } x = current->entry; delete current; count--; return success;}

  28. 线性表的实现 —单链表 • 清空单链表 template <typename list_entry>void List<list_entry>::clear() { node* nodeptr = head; while( head != NULL ) { head = nodeptr->next; delete nodeptr; nodeptr = head; } count = 0;}

  29. 线性表的实现 —单链表 • 在单链表中:☆ insert、remove、clear、replace和retrieve的时间复杂度是O(n)。☆ List、empty、full和size的时间复杂度是O(c)。 • 优点: ☆插入( insert)、删除(remove)无需移动大量元素。 ☆ 线性表容量仅受制于系统可供分配的存储空间大小。 • 缺点 ☆ 不支持元素随机存取 ☆ 需要存储指针的额外空间

  30. 线性表的实现 —改进的单链表 • 设想单链表客户程序需要多次读取(retrieve)链表中同一个元素,或总是按顺序读取单链表中的元素。 • 每次读取都需要从头指针开始遍历,效率不高。 • 可以对前述单链表实现进行改进,主要思想是记住最后一次存取过的元素的位置,如下一次存取的元素位于该元素之后,则从该元素开始遍历。若位于该元素之前,仍从头指针开始遍历。 • 注意该改进并非是在任何时侯都能提高存取效率。 • 在链表类中增加: • 指针current,保存上次访问过的结点地址 • 整型成员current_position,保存上次访问过的结点编号

  31. 线性表的实现 —改进的单链表 template <typename list_entry >class List {public: enum error_code { success, range_error, overflow, underflow};protected: struct node { ... }; int count; //线性表中元素个数 node *head; //链表头指针mutable int current_position; //上次访问过的元素的编号mutable node* current; //指向上次访问过的元素的指针void set_position(int i) const;public: //操作List(); ... void traverse( void (*visit)(list_entry&));};

  32. 线性表的实现 —改进的单链表 template <typename list_entry>void List<list_entry>::set_position(int i) const { if ( i < current_position ) { current_position=0; current = head; } for ( ; current_position<i; current_position++ ) current = current->next;} • set_position无需再返回指针,只要设置current指针即可。 • 新增的两个成员均是保护成员,对单链表的客户程序透明。 • 如果重复存取一个元素,在set_position中指针不用移动。 • 如果访问的元素位于记住的元素之前,未作任何改进。

  33. 线性表的实现 —双向链表 • 单向链表中,指针只能沿着一个方向移动,若找某结点的直接后继结点只须移动一次指针即可(O(1)),但要找某结点的直接前趋结点,则必须从头开始(O(n)) 。 • 为克服单向链表只允许单向移动指针的缺陷,引入双向链表。 • 在双向链表的结点中,有两个指针域,一个指向结点的直接前趋结点,另一个指向直接后继结点。

  34. 指向直接后继 head 指向直接前趋 空指针 线性表的实现 —双向链表 • 双向链表图示

  35. 线性表的实现 —双向链表 • 双向链表的结点设计 template <typename list_entry>struct node { list_entry entry; //数据域node *next; //指向后继的指针 node *back; //指向前趋的指针node():next(0), back(0) {} node(const list_entry &le, node* link_back=NULL, node* link_next=NULL):entry(le), back(link_back), next(link) {}};

  36. 线性表的实现 —双向链表 template <typename list_entry >class List { ...protected: struct node { list_entry entry; node* next; node* back; ... };protected:int count; //元素个数 mutable int current_position; //上次访问过的元素的编号mutable node *current; //指向上次访问过的元素的指针 void set_position(int i) const;public: //操作List(); ... void traverse( void (*visit)(list_entry&));};

  37. 线性表的实现 —双向链表 • 由于可以双向移动,双链表类中去掉了头指针,而只保留了current指针,从该指针出发可到达任何一个元素。 • 在双向链表中寻找第i个元素,首先应决定向前移动指针还是向后移动指针。 template <typename list_entry>void List<list_entry>::set_position(int i) const { if ( current_postion <= i ) for (; current_position != i; current_position++ ) current = current->next; else for (; current_position != i; current_position-- ) current = current->back;}

  38. ② ① ④ 线性表的实现 —双向链表 • 双向链表的插入

  39. 线性表的实现 —双向链表 template <typename list_entry >List<list_entry>::error_code List<list_entry >::insert(int i, const list_entry& x) { node *newnode, *previous, *following; if ( i < 0 || i > count ) return range_error; if ( i==0 ) { if ( count==0 ) following=NULL; else { set_position(0); following=current; } previous=NULL;} else { set_position( i-1); previous=current; following = previous->next; } newnode = new node(x,previous,following); if ( newnode == 0 ) return overflow; if ( previous!=NULL ) previous->next = newnode; if ( following!=NULL ) following->back = newnode; count++; current = newnode; current_position=i; return success;}

  40. 线性表的实现 —双向链表 • 双向链表在单链表的基础上提高了效率,指针移动次数减少一半,时间复杂度仍为O(n)。 • 双向链表的结点中要保存两个指针,增加了额外空间。但如果数据域较大时,增加的空间量可忽略不计。

  41. 线性表总结 • 何时选用顺序实现? • 不需要作频繁的插入和删除元素操作(尾部的插入和删除除外)。 • 需要经常随机存取元素。 • 何时选用链式实现? • 结点数据域较大时。 • 经常需要作插入和删除操作 • 经常进行顺序存取。

  42. 上机作业 • 改进顺序存储的线性表,使之容量能够动态增长,在机器上用C++实现。 • 在机器上用C++分别实现改进的单链表和双链表。

More Related