数据结构之线性表
定义
结点按照某一顺序排列形成的序列(a1,a2,a3,a4…an)称为线性表;
基本概念
a1为首结点;an为尾结点;n为线性表的长度;对于结点ax(n-1=>x>=2)则称ax-1为ax的前驱;ax+1为ax的后继。首结点没有前驱;尾结点没有后继;
如果表中的结点按照某个域的值有小到大或者由大到小排列则称该表为有序表;
顺序存储的线性表称为顺序表;链式存储的线性表称为链式表;
顺序表要求逻辑相邻的结点在物理存储上也是相邻的,所以通常使用数组实现;
链式表则没有这个要求,所以其结点结构通常为“值域”+“指针域”;值域存储业务信息,指针域存储其相邻结点的物理位置;
分类
按照逻辑关系与物理存储方式之间关系的不同,分为顺序表和链式表;
其中链式表:
按照首尾结点是否连接分为循环链表和非循环链表;
按照通过一个结点能否找到其前驱分为单向链表和双向链表;
实现
结点逻辑结构为“值域+指针域”;
结点间的逻辑关系通过“指针域”来维护;按照(单向,双向)*(循环,非循环)共有4种,结构图如下:由上到下为:
顺序表(数组实现);
单向非循环链表(指针域中有一个指针);
单向循环链表(指针域中一个指针,尾结点的后继为头结点);
双向非循环链表(指针域含两个指针,一个指向前驱,一个指向后继;头尾结点指针域只含一个指针;头结点无前驱指针;尾结点无后继指针);
双向循环链表(指针域含两个指针,一个指向前驱,一个指向后继;头结点的前驱指针指向尾结点;尾结点的后继指针指向头结点)
线性表的基本运算包括:插入、查找(按照关键字的值)、删除、排序、存取(按照索引)、反转;按照顺序表和链表各有不同;
顺序表相关算法实现要点:- 插入又可分为尾部插入和条件插入;尾部插入只需注意不要溢出即可;条件插入则包含插入点寻找以及插入后移动元素两部分。
- 查找实现比较简单,只需遍历一遍即可(未排序时,若已经排序,则可以使用二分查找法);
- 删除涉及一次查找(找到删除点)然后移动剩余节点;
- 排序算法种类繁多,暂不讨论;
- 存取,顺序表按照索引可以快速实现存取;
- 反转则可以通过两两交换位置完成;
链表相关算法实现要点:
链表的头指针的处理——头指针是指向第一个实体结点呢还是指向空节点?
头指针指向第一个实体结点(初始化置空),那么在插入前就需要判断头指针是否为空,如果为空则直接将头指针指向该元素即可,如果不为空则找到尾结点进行处理即可;好处是指针的名称和实际意义是一致的,便于其他算法的实现;坏处是每次插入时都要进行一次判断,感觉不太好;
头指针指向空节点的话(空节点没有逻辑意义,但占有物理存储空间),那么插入的时候就不需要判断,直接找到尾指针进行处理即可(尾指针初始置于空的头结点);这样的话头指针并没有指向第一个实体,所以查找元素时headPoint->next为第一个实体元素,注意就好;坏处是headPoint并不指向第一个实体元素,所以实现其他算法时需要注意避免产生错误;好处自然是插入动作可以得到一致处理;
下面就链式表下各种操作算法的实现做总结:- 插入时需要注意保护原来链表的相关信息,比如结点的前驱以及后继,插入实际上就是指针域中指针间逻辑关系的搭建;注意插入点是否在头结点之前或者尾结点之后;
- 查找时从头指针开始遍历即可;
- 删除结点则涉及到结点去掉后指针关系的重建,注意删除的结点是否是头、尾结点;
- 排序算法暂且不论;
- 存取则需要一个辅助计数器来做统计;
链表(以上提到的四种)的反转核心同插入、删除一样,都是指针关系的重建与保护,下面详细讨论:
单项不循环链表:反转需要从头开始(因为只能从头遍历嘛);需要一个结点指针指向尚未完成反转的链表头unFinishedHead;需要一个指针指向已完成反转的链表尾finishedTail;还需要一个指针指向当前操作的元素currentElement(过去、现在、将来是不是都有了,哈哈哈),反转自然是要用到while循环的,那么循环的条件便是unFinishedHead指向了NULL;在while循环体里完成指针的移动即可;循环结束后的状态因头指针的处理不同而不同:头指针指向空节点(或者第一个实体结),则原来第一个实体结点的后继空节点,尾指针指向原来的最后一个结点,即反转的过程中headPoint和tailPoint并没有移动;现在要做的就是交换头尾指针,先来看一下此时链表的结构:
finishedTail=headPoint->next;//finishedTail存储新链表的尾结点 headPointer->next=tailPoint;//头指针归位; tailPoint=finishedTial;//尾指针归位;
之所以叫关键指针是因为a1此时是通过空头元素记录的,空头元素即将指向新的第一个实体元素,所以第一步不必须保留它,然后空头元素就归位啦;之后尾指针归位即可完成反转;没有空头元素的情况比较简单,完成while循环后只需交换头尾指针即可;
单向循环链表:只有一个headPoint(其实指向结点的任何指针都可以作为headPoint,因为不论怎样都可以完成一次遍历);辅助指针同上,我们来看最后链表的状态:
是不是很像反转不带空头元素的单向非循环链表?是的,他们之间的区别只是尾元素的next指针域是否指向了头元素,因为是循环链表嘛,自然不带空头元素啦;此时需要再次成环!headPoint->next=finishedPoint;//完成循环 headPoint->finishedPoint;//如果愿意,就把头指针指向新的起始点
双向不循环链表:只要通过上面三个辅助指针完成while循环(循环里交换相关指针)然后交换头尾指针即可!
双向循环链表:思路就是while循环完毕后再修改头尾结点的前驱指针和后继指针;
这里还要记得修改这两个指针域即可;
本文源码github地址(这是自己使用Java语言(部分细节参考了Java集合类源码)实现的一套数据结构API,包括链表,栈、队列、树、图等内容,为算法学习提供基础,完善ing~)