数据结构(二
This presentation is the property of its rightful owner.
Sponsored Links
1 / 42

数据结构(二 ) PowerPoint PPT Presentation


  • 53 Views
  • Uploaded on
  • Presentation posted in: General

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

Download Presentation

数据结构(二 )

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.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -

Presentation Transcript


6214738

数据结构(二)

常宝宝

北京大学计算机科学与技术系

[email protected]


6214738

内容提要

  • 线性表的定义

  • 线性表的实现 — 顺序存储结构

  • 线性表的实现 — 链式存储结构

    • 单向链表

    • 双向链表


6214738

线性表

  • 线性表

    • n个结点(数据元素)的有限序列。

    • 始结点唯一,即存在一个结点没有前趋结点。

    • 终结点唯一,即存在一个结点没有后继结点。

    • 除始结点外,其它结点均只有一个前趋结点。

    • 除终结点外,其它结点均只有一个后继结点。

  • 线性表举例

    • 简单数据元素

      (6, 17, 28, 50, 92, 188)

      (A, B, D, E, C)

    • 复杂数据元素

      学生成绩登记表


6214738

线性表

  • 线性表中的各个数据元素具有相同(或相容)的类型。

  • 线性表中的元素个数称为线性表的长度。

  • 长度为0的线性表称为空表。

  • 线性表可视为容纳某类对象的容器。

  • 线性表中每个元素有一个表明其位置的编号,始结点编号为0,始结点的直接后继结点编号为1,…,终结点的编号为n-1。(线性表长度为n)


6214738

线性表

  • 和线性表有关的操作:

    • 构造一个空表

    • 判断线性表是否空表

    • 判断线性表是否已满

    • 返回线性表的长度

    • 将一个线性表清空

    • 在线性表的第i个位置插入一个元素

    • 删除线性表中第i个元素

    • 读取线性表中第i个元素

    • 把线性表中的第i个元素替换为另外一个元素

    • 顺序遍历线性表中的元素,并对每个元素进行特定的处理


6214738

抽象数据类型定义

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&));};


6214738

线性表操作

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


6214738

线性表操作

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:


6214738

线性表的使用

...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; //显示线性表中的元素个数 ...}


6214738

线性表的实现 —顺序存储

  • 顺序映象以存储位置先后表示元素间的前趋和后继关系。

  • 用一组地址连续的存储单元依次存放线性表中的数据元素。

a0a1…ai-1ai…an-1

线性表的起始地址

称作线性表的基地址


6214738

线性表的实现 —顺序存储

  • 若每个元素占用 l 个存储单元,用其中第一个单元的地址作为元素的存储位置。则元素 ai 的存储位置可以表示为LOC(ai)。

  • 线性表中第 i+1 个元素和第 i个元素的存储位置间满足下列关系:LOC(ai+1) = LOC(ai) + l

  • 线性表中第 i个元素的存储位置为:LOC(ai) = LOC(a0) + i * l其中LOC(a0)是线性表的基地址。

  • C++中,数组元素占据连续存储单元,可用来实现顺序存储的线性表。


6214738

线性表的实现 —顺序存储

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&));};


6214738

线性表的实现 —顺序存储

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


6214738

线性表的实现 —顺序存储

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]);}


6214738

线性表的实现 —顺序存储

  • 元素插入操作的时间复杂度分析

  • 假设在第i个位置插入元素的概率为pi,则在长度为n 的线性表中插入一个元素所需移动元素次数的期望值为:

  • 假定在线性表中任何一个位置上进行插入的概率都是相等的,则移动元素的期望值为:

  • 在线性表中插入一个元素平均需要移动一半元素,时间复杂度是线性阶,即O(n)。


6214738

线性表的实现 —顺序存储

  • 在顺序实现中:☆ insert 和 remove 的时间复杂度是O(n)。☆ List、clear、empty、full、size、replace和retrieve的时间复杂度是O(c)。

  • 优点: ☆ 随机存取( retrieve )

  • 缺点 ☆ 插入( insert )、删除( remove )需移动大量元素 ☆ 需要预先估计所需最大空间 ☆ 表的容量不能动态扩充

  • 思考:如何实现容量可动态增长的顺序存储的线性表?


6214738

线性表的实现 —单链表

  • 用一组地址任意的存储单元存放线性表中的数据元素。用附加的指针指示元素的前趋和后继关系。

  • 为了表示数据元素ai 与其直接后继元素ai+1之间的逻辑关系,对数据元素ai 而言,除存储其本身的信息外,还需要存储指示其后继的指针(地址)信息。这两部分合起来通常称为结点。结点 = 元素 + 指针

  • n个结点通过指针链接形成一个链表,即为线性表的链式存储结构。由于每个结点中包含一个指针域,所以称为单链表。


6214738

空指针

head

a0 a1 … ... an^

线性表的实现 —单链表

  • 整个链表的存取必须从头指针开始进行,头指针指示链表中的第一个结点的存储位置。

  • 最后一个元素由于没有后继结点,最后一个结点的指针应为空(NULL)。

  • 在单链表中,数据元素之间的逻辑关系是由结点中的指针指示的,逻辑上相邻的两个元素,其存储的物理位置不必紧邻。

  • 在线性表的顺序存储结构中,逻辑上相邻的元素物理位置紧邻,因而任何一个元素的存储位置都可以从线性表的起始位置计算得到,因而可实现随机存取。


6214738

线性表的实现 —单链表

  • 在单链表中,任何两个元素的存储位置之间没有固定联系。每个元素的存储位置只能通过读取其前趋结点的指针域得到。

  • 在单链表中,读取第i个数据元素必须从头指针出发寻找,单链表是非随机存取的存储结构。


6214738

线性表的实现 —单链表

  • 在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) {}};


6214738

线性表的实现 —单链表

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&));};


6214738

线性表的实现 —单链表

  • 为了实现单链表的其它操作,定义一个支持函数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;}


6214738

线性表的实现 —单链表

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


6214738

线性表的实现 —单链表

  • 在单链表中插入无需大量移动元素。


6214738

线性表的实现 —单链表

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


6214738

线性表的实现 —单链表

  • 在单链表中删除元素无需移动大量元素


6214738

线性表的实现 —单链表

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


6214738

线性表的实现 —单链表

  • 清空单链表

template <typename list_entry>void List<list_entry>::clear() { node* nodeptr = head; while( head != NULL ) { head = nodeptr->next; delete nodeptr; nodeptr = head; } count = 0;}


6214738

线性表的实现 —单链表

  • 在单链表中:☆ insert、remove、clear、replace和retrieve的时间复杂度是O(n)。☆ List、empty、full和size的时间复杂度是O(c)。

  • 优点: ☆插入( insert)、删除(remove)无需移动大量元素。 ☆ 线性表容量仅受制于系统可供分配的存储空间大小。

  • 缺点 ☆ 不支持元素随机存取 ☆ 需要存储指针的额外空间


6214738

线性表的实现 —改进的单链表

  • 设想单链表客户程序需要多次读取(retrieve)链表中同一个元素,或总是按顺序读取单链表中的元素。

  • 每次读取都需要从头指针开始遍历,效率不高。

  • 可以对前述单链表实现进行改进,主要思想是记住最后一次存取过的元素的位置,如下一次存取的元素位于该元素之后,则从该元素开始遍历。若位于该元素之前,仍从头指针开始遍历。

  • 注意该改进并非是在任何时侯都能提高存取效率。

  • 在链表类中增加:

    • 指针current,保存上次访问过的结点地址

    • 整型成员current_position,保存上次访问过的结点编号


6214738

线性表的实现 —改进的单链表

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&));};


6214738

线性表的实现 —改进的单链表

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中指针不用移动。

  • 如果访问的元素位于记住的元素之前,未作任何改进。


6214738

线性表的实现 —双向链表

  • 单向链表中,指针只能沿着一个方向移动,若找某结点的直接后继结点只须移动一次指针即可(O(1)),但要找某结点的直接前趋结点,则必须从头开始(O(n)) 。

  • 为克服单向链表只允许单向移动指针的缺陷,引入双向链表。

  • 在双向链表的结点中,有两个指针域,一个指向结点的直接前趋结点,另一个指向直接后继结点。


6214738

指向直接后继

head

指向直接前趋

空指针

线性表的实现 —双向链表

  • 双向链表图示


6214738

线性表的实现 —双向链表

  • 双向链表的结点设计

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) {}};


6214738

线性表的实现 —双向链表

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&));};


6214738

线性表的实现 —双向链表

  • 由于可以双向移动,双链表类中去掉了头指针,而只保留了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;}


6214738

线性表的实现 —双向链表

  • 双向链表的插入


6214738

线性表的实现 —双向链表

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


6214738

线性表的实现 —双向链表

  • 双向链表在单链表的基础上提高了效率,指针移动次数减少一半,时间复杂度仍为O(n)。

  • 双向链表的结点中要保存两个指针,增加了额外空间。但如果数据域较大时,增加的空间量可忽略不计。


6214738

线性表总结

  • 何时选用顺序实现?

    • 不需要作频繁的插入和删除元素操作(尾部的插入和删除除外)。

    • 需要经常随机存取元素。

  • 何时选用链式实现?

    • 结点数据域较大时。

    • 经常需要作插入和删除操作

    • 经常进行顺序存取。


6214738

上机作业

  • 改进顺序存储的线性表,使之容量能够动态增长,在机器上用C++实现。

  • 在机器上用C++分别实现改进的单链表和双链表。


  • Login