第3章链表 第3章链表 单链表的结点类( List Node class)和链表类( ist class的类定义 前视的类定义 template <class Type> class ListNode i ∥链表结点类的定义 friend class List<Type>: List类作为友元类定义 ListNode<Type> *link ∥链指针域 public: ListNode(): link(NULL)0 ∥仅初始化指针成员的构造函数 ListNode( const Type& item ): data(item), link(NULL)& ∥初始化数据与指针成员的构造函数 ListNode<Type> *getNode( const Type& item, ListNode<Type> *next= NULL 以tem和next建立一个新结点 ListNode<Iype>* gatLin){ return link;}∥得结点的下一结点地址 Type getData(i return data; 3 取得结点中的数据 void setLink( ListNode<Type> *next)i link =next; 3 ∥修改结点的link指针 void setData( Type value )( data= value; ∥修改结点的data值 template <class Type> class List i ∥单链表类定义 private: ListNode<Type> ∥链表的表头指针和当前元素指针 public: List( const Type& value)( first =current = new ListNode<Type>( value );3 ∥构造函数 -List () MakeEmpty () delete first;) ∥析构函数 Make Empty ( ∥将链表置为空表 int Length()const; ∥计算链表的长度 ListNode<Type> * Find ( Type value ) ∥搜索含数据 value的元素并成为当前元素 ListNode<Type> *Locate( int 1); ∥搜索第i个元素的地址并置为当前元素 Type* GetData (; /取出表中当前元素的值 int Insert( Type value ) ∥将vaue插在表当前位置之后并成为当前元素 ∥将链表中的当前元素删去,填补者为当前元素 ListNode<type> *Firster (i current first; return first; ∥当前指针定位于表头结点 Type*First ( ∥当前指针定位于表中第一个元素并返回其值 Type"Next ( 将当前指针进到表中下一个元素并返回其值 int notnull(){ return current=NUL;}表中当前元素空否?空返回1,不空返回0 int NextNotNull (i return current NULL & current->link ! NULL; ∥当前元素下一元素空否?空返回1,不空返回0 };
第 3 章 链表 21 第 3 章 链表 单链表的结点类(ListNode class)和链表类(List class)的类定义。 template <class Type> class List; //前视的类定义 template <class Type> class ListNode { //链表结点类的定义 friend class List<Type>; //List 类作为友元类定义 private: Type data; //数据域 ListNode<Type> *link; //链指针域 public: ListNode ( ) : link (NULL) { } //仅初始化指针成员的构造函数 ListNode ( const Type& item ) : data (item), link (NULL) { } //初始化数据与指针成员的构造函数 ListNode<Type> * getNode ( const Type& item, ListNode<Type> *next = NULL ) //以 item 和 next 建立一个新结点 ListNode<Type> * getLink ( ) { return link; } //取得结点的下一结点地址 Type getData ( ) { return data; } //取得结点中的数据 void setLink ( ListNode<Type> * next ) { link = next; } //修改结点的 link 指针 void setData ( Type value ) { data = value; } //修改结点的 data 值 }; template <class Type> class List { //单链表类定义 private: ListNode<Type> *first, *current; //链表的表头指针和当前元素指针 public: List ( const Type& value ) { first = current = new ListNode<Type> ( value ); } //构造函数 ~List ( ) { MakeEmpty ( ); delete first; } //析构函数 void MakeEmpty ( ); //将链表置为空表 int Length ( ) const; //计算链表的长度 ListNode<Type> * Find ( Type value ); //搜索含数据 value 的元素并成为当前元素 ListNode<Type> * Locate( int i ); //搜索第 i 个元素的地址并置为当前元素 Type * GetData ( ); //取出表中当前元素的值 int Insert ( Type value ); //将 value 插在表当前位置之后并成为当前元素 Type *Remove ( ); //将链表中的当前元素删去, 填补者为当前元素 ListNode<Type> * Firster ( ) { current = first; return first; } //当前指针定位于表头结点 Type *First ( ); //当前指针定位于表中第一个元素并返回其值 Type *Next ( ); //将当前指针进到表中下一个元素并返回其值 int NotNull ( ) { return current != NULL; } //表中当前元素空否?空返回 1, 不空返回 0 int NextNotNull ( ) { return current != NULL && current->link != NULL; } //当前元素下一元素空否?空返回 1, 不空返回 0 };
第3章链表 3-1线性表可用顺序表或链表存储。试问 (1)两种存储表示各有哪些主要优缺点? 、(2)如果有n个表同时并存,并且在处理过程中各表的长度会动态发生变化,表的总数也可能自 改变、在此情况下,应选用哪种存储表示?为什么? 、(B)若表的总数基本稳定,且很少进行插入和删除,但要求以最快的速度存取表中的元素,这时, 采用哪种存储表示?为什 【解答】 (1)顺序存储表示是将数据元素存放于一个连续的存储空间中,实现顺序存取或(按下标)直接存 取。它的存储效率高,存取速度快。但它的空间大小一经定义,在程序整个运行期间不会发生改变 因此,不易扩充。同时,由于在插入或删除时,为保持原有次序,平均需要移动一半(或近一半)元素, 修改效率不高 链接存储表示的存储空间一般在程序的运行过程中动态分配和释放,且只要存储器中还有空间, 就不会产生存储溢出的问题。同时在插入和删除时不需要保持数据元素原来的物理顺序,只需要保持 原来的逻辑顺序,因此不必移动数据,只需修改它们的链接指针,修改效率较高。但存取表中的数据 元素时,只能循链顺序访问,因此存取效率不高 (2)如果有n个表同时并存,并且在处理过程中各表的长度会动态发生变化,表的总数也可能自 动改变、在此情况下,应选用链接存储表示。 如果采用顺序存储表示,必须在一个连续的可用空间中为这n个表分配空间。初始时因不知道哪 个表增长得快,必须平均分配空间。在程序运行过程中,有的表占用的空间增长得快,有的表占用的 空间增长得慢:有的表很快就用完了分配给它的空间,有的表才用了少量的空间,在进行元素的插入 时就必须成片地移动其他的表的空间,以空出位置进行插入:在元素删除时,为填补空白,也可能移 动许多元素。这个处理过程极其繁琐和低效 如果采用链接存储表示,一个表的存储空间可以连续,可以不连续。表的增长通过动态存储分配 解决,只要存储器未满,就不会有表溢出的问题:表的收缩可以通过动态存储释放实现,释放的空间 还可以在以后动态分配给其他的存储申请要求,非常灵活方便。对于n个表(包括表的总数可能变化) 共存的情形,处理十分简便和快捷。所以选用链接存储表示较好 (3)应采用顺序存储表示。因为顺序存储表示的存取速度快,但修改效率低。若表的总数基本稳 定,且很少进行插入和删除,但要求以最快的速度存取表中的元素,这时采用顺序存储表示较好 3-2针对带表头结点的单链表,试编写下列函数。 (1)定位函数 Locate:在单链表中寻找第i个结点。若找到,则函数返回第i个结点的地址;若找 不到,则函数返回NUL (2)求最大值函数max:通过一趟遍历在单链表中确定值最大的结点。 (3)统计函数 number:统计单链表中具有给定值x的所有元素 (4)建立函数 create:根据一维数组an建立一个单链表,使单链表中各元素的次序与a[n中各元 素的次序相同,要求该程序的时间复杂性为O(n (5)整理函数 tidyup:在非递减有序的单链表中删除值相同的多余结点 【解答】 (1)实现定位函数的算法如下: template <class Type> ListNode <Type>*List <Type>: Locate( int i)& ∥取得单链表中第i个结点地址,i从1开始计数,i<=0时返回指针NULL if (i<=0)return NULL ∥位置i在表中不存在 ListNode <Type>*p=first; int k=0; ∥从表头结点开始检测
第 3 章 链表 22 3-1 线性表可用顺序表或链表存储。试问: (1) 两种存储表示各有哪些主要优缺点? (2) 如果有 n 个表同时并存,并且在处理过程中各表的长度会动态发生变化,表的总数也可能自 动改变、在此情况下,应选用哪种存储表示?为什么? (3) 若表的总数基本稳定,且很少进行插入和删除,但要求以最快的速度存取表中的元素,这时, 应采用哪种存储表示?为什么? 【解答】 (1) 顺序存储表示是将数据元素存放于一个连续的存储空间中,实现顺序存取或(按下标)直接存 取。它的存储效率高,存取速度快。但它的空间大小一经定义,在程序整个运行期间不会发生改变, 因此,不易扩充。同时,由于在插入或删除时,为保持原有次序,平均需要移动一半(或近一半)元素, 修改效率不高。 链接存储表示的存储空间一般在程序的运行过程中动态分配和释放,且只要存储器中还有空间, 就不会产生存储溢出的问题。同时在插入和删除时不需要保持数据元素原来的物理顺序,只需要保持 原来的逻辑顺序,因此不必移动数据,只需修改它们的链接指针,修改效率较高。但存取表中的数据 元素时,只能循链顺序访问,因此存取效率不高。 (2) 如果有 n 个表同时并存,并且在处理过程中各表的长度会动态发生变化,表的总数也可能自 动改变、在此情况下,应选用链接存储表示。 如果采用顺序存储表示,必须在一个连续的可用空间中为这 n 个表分配空间。初始时因不知道哪 个表增长得快,必须平均分配空间。在程序运行过程中,有的表占用的空间增长得快,有的表占用的 空间增长得慢;有的表很快就用完了分配给它的空间,有的表才用了少量的空间,在进行元素的插入 时就必须成片地移动其他的表的空间,以空出位置进行插入;在元素删除时,为填补空白,也可能移 动许多元素。这个处理过程极其繁琐和低效。 如果采用链接存储表示,一个表的存储空间可以连续,可以不连续。表的增长通过动态存储分配 解决,只要存储器未满,就不会有表溢出的问题;表的收缩可以通过动态存储释放实现,释放的空间 还可以在以后动态分配给其他的存储申请要求,非常灵活方便。对于 n 个表(包括表的总数可能变化) 共存的情形,处理十分简便和快捷。所以选用链接存储表示较好。 (3) 应采用顺序存储表示。因为顺序存储表示的存取速度快,但修改效率低。若表的总数基本稳 定,且很少进行插入和删除,但要求以最快的速度存取表中的元素,这时采用顺序存储表示较好。 3-2 针对带表头结点的单链表,试编写下列函数。 (1) 定位函数 Locate:在单链表中寻找第 i 个结点。若找到,则函数返回第 i 个结点的地址;若找 不到,则函数返回 NULL。 (2) 求最大值函数 max:通过一趟遍历在单链表中确定值最大的结点。 (3) 统计函数 number:统计单链表中具有给定值 x 的所有元素。 (4) 建立函数 create:根据一维数组 a[n]建立一个单链表,使单链表中各元素的次序与 a[n]中各元 素的次序相同,要求该程序的时间复杂性为 O(n)。 (5) 整理函数 tidyup:在非递减有序的单链表中删除值相同的多余结点。 【解答】 (1) 实现定位函数的算法如下: template <class Type> ListNode <Type> * List <Type> :: Locate ( int i ) { //取得单链表中第 i 个结点地址, i 从 1 开始计数, i <= 0 时返回指针 NULL if ( i <= 0 ) return NULL; //位置 i 在表中不存在 ListNode <Type> * p = first; int k = 0; //从表头结点开始检测
第3章链表 while( l=NULL &&k<i)p= p->link; k++1 ∥循环,p=NUIL表示链短,无第i个结点 return p; ∥若pl=NULL,则k=i返回第i个结点地址 (2)实现求最大值的函数如下: template <class Type> ListNode <Type>.List <Type>: Max ()i ∥在单链表中进行一趟检测,找出具有最大值的结点地址,如果表空,返回指针NULL if first-> link nulL )return NULL ∥空表,返回指针NUIL ListNode<Iype>*pmax=fist->link,p=fist->link->link;∥假定第一个结点中数据具有最大值 while(p I= NULL W循环,下一个结点存在 if( p->data >pma ∥指针pmax记忆当前找到的具最大值结点 ∥检测下一个结点 return pmax (3)实现统计单链表中具有给定值x的所有元素的函数如下 template <class Type> int List <Type>: Count(Type& x)i ∥在单链表中进行一趟检测,找出具有最大值的结点地址,如果表空,返回指针NULL ListNode <Type>*p=first->link ∥从第一个结点开始检测 while(p I= NULL )i ∥循环,下一个结点存在 if(p->data =x)n++; ∥找到一个,计数器加1 ∥检测下一个结点 return n: (4)实现从一维数组A[m建立单链表的函数如下: template <class Type> void List <Type>: Create( Type A[, int n)& ∥根据一维数组A[n]建立一个单链表,使单链表中各元素的次序与A[n]中各元素的次序相同 first=p= new ListNode<Type 建表头结点 for( int i=0;i<n; i++)i p->link =new ListNode<Type>(A[); ∥链入一个新结点,值为A ∥指针p总指向链中最后一个结点 p->link NULL 采用递归方法实现时,需要通过引用参数将已建立的单链表各个结点链接起来。为此,在递归地 扫描数组A[n]的过程中,先建立单链表的各个结点,在退出递归时将结点地址p(被调用层的形参) 带回上一层(调用层)的实参p->link template<Type> void List<Type>: create( Type all, int n, int 1, ListNode<Type> *&p)i ∥有函数:递归调用建立单链表 if(i==n)p=NULL; else( p= new ListNode<Type>( A[]) ∥建立链表的新结点
第 3 章 链表 23 while ( p != NULL && k < i ) { p = p->link; k++; } //循环, p == NULL 表示链短, 无第 i 个结点 return p; //若 p != NULL, 则 k == i, 返回第 i 个结点地址 } (2) 实现求最大值的函数如下: template <class Type> ListNode <Type> * List <Type> :: Max ( ) { //在单链表中进行一趟检测,找出具有最大值的结点地址, 如果表空, 返回指针 NULL if ( first->link == NULL ) return NULL; //空表, 返回指针 NULL ListNode <Type> * pmax = first->link, p = first->link->link; //假定第一个结点中数据具有最大值 while ( p != NULL ) { //循环, 下一个结点存在 if ( p->data > pmax->data ) pmax = p; //指针 pmax 记忆当前找到的具最大值结点 p = p->link; //检测下一个结点 } return pmax; } (3) 实现统计单链表中具有给定值 x 的所有元素的函数如下: template <class Type> int List <Type> :: Count ( Type& x ) { //在单链表中进行一趟检测,找出具有最大值的结点地址, 如果表空, 返回指针 NULL int n = 0; ListNode <Type> * p = first->link; //从第一个结点开始检测 while ( p != NULL ) { //循环, 下一个结点存在 if ( p->data == x ) n++; //找到一个, 计数器加 1 p = p->link; //检测下一个结点 } return n; } (4) 实现从一维数组 A[n]建立单链表的函数如下: template <class Type> void List <Type> :: Create ( Type A[ ], int n ) { //根据一维数组 A[n]建立一个单链表,使单链表中各元素的次序与 A[n]中各元素的次序相同 ListNode<Type> * p; first = p = new ListNode<Type>; //创建表头结点 for ( int i = 0; i < n; i++ ) { p->link = new ListNode<Type> ( A[i] ); //链入一个新结点, 值为 A[i] p = p->link; //指针 p 总指向链中最后一个结点 } p->link = NULL; } 采用递归方法实现时,需要通过引用参数将已建立的单链表各个结点链接起来。为此,在递归地 扫描数组 A[n]的过程中,先建立单链表的各个结点,在退出递归时将结点地址 p(被调用层的形参) 带回上一层(调用层)的实参 p->link。 template<Type> void List<Type> :: create ( Type A[ ], int n, int i, ListNode<Type> *& p ) { //私有函数:递归调用建立单链表 if ( i == n ) p = NULL; else { p = new ListNode<Type>( A[i] ); //建立链表的新结点
第3章链表 create(A, n, i+1, p->link ) ∥递归返回时p->link中放入下层p的内容 template<Type> void List<Type> : create( Type A[l, int n )i ∥外部调用递归过程的共用函数 first=current=new ListNode<Typ ∥建立表头结点 create(A, n, 0, first->link ) ∥递弟归建立单链表 (5)实现在非递减有序的单链表中删除值相同的多余结点的函数如下 template <class Type> void List <Type>:: tidyup (i ListNode<Type> *p= first-> link, temp: ∥检测指针,初始时指向链表第一个结点 while( p I= nULL & p->link I= NULL ∥循环检测链表 if( p->data ==p->link->data )t ∥若相邻结点所包含数据的值相等 temp= p->first; p->link temp-> link ∥为删除后一个值相同的结点重新拉链 delete temp ∥删除后一个值相同的结点 ∥指针p进到链表下一个结点 3-3设ha和hb分别是两个带表头结点的非递减有序单链表的表头指针,试设计一个算法,将这两个有 序链表合并成一个非递增有序的单链表。要求结果链表仍使用原来两个链表的存储空间,不另外占用 其它的存储空间。表中允许有重复的数据。 【解答】 #include <iostream h> template <class Type> class List; template <class Type> class ListNode friend class List<Type>: ∥构造函数 ListNode const Type& item ) ∥构造函数 private Type data; template <class Type> class List i List( const Type finishied ) ∥建立链表 void Browse(片; 印链表 void Merge( List<Type> &hb )i ∥连接链表 ListNode<Type> "first, 'last;
第 3 章 链表 24 create ( A, n, i+1, p->link ); //递归返回时 p->link 中放入下层 p 的内容 } } template<Type> void List<Type> :: create ( Type A[ ], int n ) { //外部调用递归过程的共用函数 first = current = new ListNode<Type>; //建立表头结点 create ( A, n, 0, first->link ); //递归建立单链表 } (5) 实现在非递减有序的单链表中删除值相同的多余结点的函数如下: template <class Type> void List <Type> :: tidyup ( ) { ListNode<Type> * p = first->link, temp; //检测指针, 初始时指向链表第一个结点 while ( p != NULL && p->link != NULL ) //循环检测链表 if ( p->data == p->link->data ) { //若相邻结点所包含数据的值相等 temp = p->first; p->link = temp->link; //为删除后一个值相同的结点重新拉链 delete temp; //删除后一个值相同的结点 } else p = p->link; //指针 p 进到链表下一个结点 } 3-3 设 ha 和hb 分别是两个带表头结点的非递减有序单链表的表头指针, 试设计一个算法, 将这两个有 序链表合并成一个非递增有序的单链表。要求结果链表仍使用原来两个链表的存储空间, 不另外占用 其它的存储空间。表中允许有重复的数据。 【解答】 #include <iostream.h> template <class Type> class List; template <class Type> class ListNode { friend class List<Type>; public: ListNode ( ); //构造函数 ListNode ( const Type& item ); //构造函数 private: Type data; ListNode<Type> *link; }; template <class Type> class List { public: List ( const Type finishied ); //建立链表 void Browse ( ); //打印链表 void Merge ( List<Type> &hb ); //连接链表 private: ListNode<Type> *first, *last; };
第3章链表 ∥.成员函数的实现 template <class Type> ListNode<Type> ListNode ( link( NULL 0 ∥构造函数,仅初始化指针成员。 template <class Type> ListNode<Type>:: ListNode( const Type item ) data( item ) link( NULL) ∥构造函数,初始化数据与指针成员。 template <class Type> List<Type>: List( const Type finishied )i ∥创建一个带表头结点的有序单链表, finished是停止建表输入标志,是所有输入值中不可能出现的数值 first =last =new ListNode<Type>( ) ∥创建表头结点 Type value; ListNode<Type> *p, *g,*s: cin > value while( value 环建立各个结点 s= new ListNode<Type>( value ) while(p l= NULL &&p->data<=value) 找新结点插入位置 q->link =s; /在q,p间插入新结点 if( p= NULL )last=s cin >> value: template <class Type> void List<Type>: Browse(i ∥浏览并输出链表的内容 cout<<\nThe list is \n" ListNode<Type>"p= first->link while(p I= NULL)t cout < p->data; if( pI= last )cout <<"->i else cout < endI: p= p->link; template <class Type> void List <Type>:: Merge( List<Type>& hb) ∥将当前链表this与链表hb按逆序合并,结果放在当前链表this中。 a= first->link; pb=hb. first->link ∥检测指针跳过表头结点 first->link NULL ∥结果链表初始化 while( pa= NULL & pb I= NULL )t 当两链表都未结束时 if( pa->data <=pb->data (q=pa: pa= pa->link; j ∥从pa链中摘下
第 3 章 链表 25 //各成员函数的实现 template <class Type> ListNode<Type> :: ListNode ( ) : link ( NULL ) { } //构造函数, 仅初始化指针成员。 template <class Type> ListNode<Type> :: ListNode ( const Type & item ) : data ( item ), link ( NULL ) { } //构造函数, 初始化数据与指针成员。 template <class Type> List<Type> :: List ( const Type finishied ) { //创建一个带表头结点的有序单链表, finished 是停止建表输入标志, 是所有输入值中不可能出现的数值。 first = last = new ListNode<Type>( ); //创建表头结点 Type value; ListNode<Type> *p, *q, *s; cin >> value; while ( value != finished ) { //循环建立各个结点 s = new ListNode<Type>( value ); q = first; p = first->link; while ( p != NULL && p->data <= value ) { q = p; p = p->link; } //寻找新结点插入位置 q->link = s; s->link = p; //在 q, p 间插入新结点 if ( p == NULL ) last = s; cin >> value; } } template <class Type> void List<Type> :: Browse ( ) { //浏览并输出链表的内容 cout<<"\nThe List is : \n"; ListNode<Type> *p = first->link; while ( p != NULL ) { cout << p->data; if ( p != last ) cout << "->"; else cout << endl; p = p->link; } } template <class Type> void List <Type> :: Merge ( List<Type>& hb) { //将当前链表 this 与链表 hb 按逆序合并,结果放在当前链表 this 中。 ListNode<Type> *pa, *pb, *q, *p; pa = first->link; pb = hb.first->link; //检测指针跳过表头结点 first->link = NULL; //结果链表初始化 while ( pa != NULL && pb != NULL ) { //当两链表都未结束时 if ( pa->data <= pb->data ) { q = pa; pa = pa->link; } //从 pa 链中摘下