说明:算法预备军系列内容均为个人的学习笔记,主要是指数据结构方面的,后面在继续学习的过程中会陆续分享相关内容.数据结构这块主要学习来源为<大话数据结构这本书>,大家不喜欢摘录的,可以自行看书.
线性表:零个或多个数据元素的有限序列.
首先它是一个序列.也就是说,元素之间是有顺序的,若元素存在多个,则第一个元素无前驱,最后一个元素无后继,其他每个元素都有且只有一个前驱和后继.
然后线性表强调是有限的.事实上,在计算机中处理的对象都是有限的,那种无限的序列,只存在于数学的概念中.
如果用数学语言来进行定义,定义是:若将线性表记为(a
1,
..., a
i-1, a
i, a
i+1, .... , a
n),则表中a
i-1领先于a
i,a
i领先于a
i+1,称a
i-1是a
i的直接前驱元素,a
i+1是a
i的直接后继元素.当i=1,2,...,n-1时,a
i有且仅有一个直接后继,当i=2,3,..,n时,a
i有且仅有一个直接前驱.
所以线性表元素的个数n(n>=0)定义为线性表的长度,当n=0时,称为空表.
在非空表中的每个数据元素都有一个确定的位置,如a
1是第一个数据元素,a
n是最后一个数据元素,a
i是第i个数据元素,称i为数据元素a
i在线性表中的位序.
线性表的抽象数据类型:
线性表的抽象数据类型定义如下:
ADT 线性表(List)
Data
线性表的数据对象集合为{a1,a2,........,an},每个元素的类型均为DataType.其中,除第一个元素a1外,每一个元素有且仅有一个直接前驱元素,除了最后一个元素an外,每一个元素有且只有一个直接后继元素.数据元素之间的关系是一对一的关系.
Operation
InitList (*L):初始化操作,建立一个空的线性表L
ListEmpty(L):若线性表为空,返回true,否则返回false
ClearList(*L):将线性表清空
GetElem(L,i,*e):将线性表L中的第i个位置元素值返回给e
LocateElem(L,e):在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则,返回0表示失败
ListInsert(*L,i,e):在线性表L中的第i个位置插入新元素e
ListDelete(*L,i,e):删除线性表L中的第i个位置元素,并用e返回其值
ListLength(L):返回线性表L的元素个数
endADT
对于不同的应用,线性表的基本操作是不同的,上述操作是最基本的,对于实际问题中涉及的关于线性表的更复杂的操作,完全可以用这些基本操作的组合来实现.
线性表的顺序存储结构:
线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素.
线性表的顺序存储的结构代码:
#define MAXSIZE 20 /*存储空间初始分配量*/
typedef int ElemType; /*ElemType类型根据实际情况而定,这里假设为int*/
typedef struct
{
ElemType data[MAXSIZE]; /*数组存储数据元素,最大值为MAXSIZE*/
int length; /*线性表当前长度*/
}SqlList;
这里,我们就发现描述顺序存储结构需要三个属性:
(1)存储空间的起始位置:数据data,它的存储位置就是存储空间的存储位置
(2)线性表的最大存储容量:数组长度MAXSIZE
(3)线性表的当前长度:length
数据长度与线性表长度区别
数组的长度是存放线性表的存储空间的长度,存储分配后这个量一般是不变的.
线性表的长度是线性表中数据元素的个数,随着线性表插入和删除操作的进行,这个量是变化的.
线性表的顺序存储结构,在存,读数据时,不管是哪个位置,时间复杂度都是O(1);而插入或删除时,时间复杂度都是O(n).这就说明,它比较适合元素个数不太变化,而更多是存储数据的应用.当然,它的优缺点还不只这些.......
线性表顺序存储结构的优缺点:
优点:(1)无须为表示表中元素之间的逻辑关系而增加额外的存储空间(2)可以快速地存取表中任一位置的元素.
缺点:(1)插入和删除操作需要移动大量元素(2)当线性表长度变化较大时,难以确定存储空间的容量(3)造成存储空间的"碎片"
线性表的链式存储结构:
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的.这就意味着,这些数据元素可以存在内存未被占用的任意位置.以前在顺序结构中,每个数据元素只需要存数据元素信息就可以了.现在链式结构中,除了要存数据元素信息外,还要存储它的后继元素的存储地址.
因此,为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需要存储一个指示其直接后继的信息(即直接后继的存储位置).我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域.指针域中存储的信息称做指针或链.这两部分信息组成数据元素ai的存储印象,称为结点.
n个结点(ai的存储映像)链结成一个链表,即为线性表(a1,a2,...,an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表.单链表正是通过每个结点的指针域将线性表的数据元素按其逻辑次序链接在一起.
n个结点(a
i的存储印象)链结成一个链表,即为线性表(a
1,a
2,...,a
n)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。
单链表正是通过每个结点的指针域将线性表的数据元素按其逻辑次序链接在一起。
对于线性表来说,总得有个头有个尾,链表也不例外.我们把链表中第一个结点的存储位置叫做头指针,那么整个链表的存取就必须是从头指针开始进行了.之后的每一个结点,其实就是上一个的后继指针指向的位置
线性链表的最后一个结点指针为"空"(通常用NULL或"^"符号表示)
有时,我们为了更加方便地对链表进行操作,会在单链表的第一个结点前附设一个结点,称为头结点.头结点的数据域可以不存储任何信息,也可以存储如线性表的长度等附加信息,头结点的指针域存储指向第一个结点的指针.
头指针与头结点的异同:
头指针:(1)头指针是指链表指向第一个结点的指针.若链表有头结点,则是指向头结点的指针.(2)头指针具有标识作用,所以常用头指针冠以链表的名字(3)无论链表是否为空,头指针均不为空.头指针是链表的必要元素
头结点:(1)头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义(也可存放链表的长度)(2)有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其它结点的操作就统一了(3)头结点不一定是链表必须要素
线性表的链式存储的主要形式为:单链表,静态链表,循环链表,双向链表。
单链表:
线性表的单链表存储结构:
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList;/*定义LinkList*/
从这个结构定义中,我们也就知道,结点由存放数据元素的数据域存放后继结点地址的指针域组成。
单链表的整表创建有头插法和尾插法两种方式。头插法就是始终让新结点在第一的位置,而尾插法则是每次把新结点都插在终端结点的后面
。
单链表结构与顺序存储结构优缺点:
(1)存储分配方式:顺序存储结构用一段连续的存储单元依次存储线性表的数据元素。单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素。
(2)时间性能:查找——顺序存储结构o(1),单链表o(n);插入和删除——顺序存储结构需要平均移动表长一半的,时间为o(n),单链表在线性出某位置的指针后,插入和删除时间仅为o(1)
(3)空间性能:顺序存储结构需要预分配存储空间,分大了,浪费,分小了易发生上溢;单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制。
静态链表就是申请足够大的连续地址空间,然后利用数组下标去代表数据元素的地址的特殊的链表。我们把这种用数组描述的链表叫做静态链表,这种描述方法还有起名叫做游标实现法.
为了我们方便插入数据,我们通常会把数组建立得大一些,以便有一些空闲空间可以便于插入时不至于溢出。在c语言中的静态链表的存储结构如下:
#define MAXSIZE 1000 /*假设链表的最大长度是1000*/
typedef struct
{
ElemType data;
int cur; /*游标(Cursor),为0时表示无指向*/
}Component,StatticLinkList[MAXSIZE];
另外我们对数组第一个和最后一个元素作为特殊元素处理,不存数据。我们通常把未被使用的数组元素称为备用链表。而数组第一个元素,即下标为0的元素的cur就存放备用链表的第一个结点的下标;而数组的最后一个元素的cur则存放第一个有数值的元素的下标,相当于单链表中的头结点作用,当整个链表为空时,则最后一个元素的cur为0。
静态链表的优缺点:
优点:在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中插入和删除操作需要移动大量元素的缺点。
缺点:没有解决连续存储分配带来的表长难以确定的问题。
静态链表其实是为了给没有指针的高级语言设计的一种实现单链表能力的方法,大家只需要理解这样设计的思想。
循环链表:将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称为循环链表。
双向链表:是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。
有关线性表的基本概念就这么多,简单概括为下面的表格:
线性表 | ||||
顺序存储结构 | 链式存储结构 | |||
单链表 | 静态链表 | 循环链表 | 双向链表 |
/**
*
* @author GeneralAndroid
*
*/
public class SingleLink {
private Student singleLink;
public static void main(String[] args) {
SingleLink singleLink=new SingleLink();
singleLink.InitList();
singleLink.ListInsert(1, new Student("first","1"));
singleLink.ListInsert(2, new Student("second","2"));
singleLink.ListInsert(3, new Student("thrid","3"));
singleLink.ListInsert(4, new Student("four","4"));
singleLink.ListInsert(5, new Student("five","5"));
singleLink.ListInsert(6, new Student("six","6"));
singleLink.ListInsert(7, new Student("seven","7"));
singleLink.ListInsert(8, new Student("eight","8"));
System.out.println("Single List is empty:"+singleLink.ListEmpty());
System.out.println("第3个元素是:"+singleLink.GetElem(3).name);
System.out.println("new Student(\"four\",\"4\"):"+singleLink.LocateElem(new Student("four","4")));
System.out.println("Single List length:"+singleLink.ListLength());
System.out.println("delete elem is :"+singleLink.ListDelete(8).name);;
System.out.println("Single List length:"+singleLink.ListLength());
singleLink.ClearList();
System.out.println("Single List is empty after clear:"+singleLink.ListEmpty());
System.out.println("Single List length:"+singleLink.ListLength());
}
/**初始化操作,建立一个空的线性表**/
public void InitList(){
singleLink=new Student();
singleLink.name="Head Data";
singleLink.num="0";
singleLink.next=null;
}
/**若线性表为空,返回true,否则返回false**/
public boolean ListEmpty(){
if(singleLink==null){
return true;
}else if(singleLink.next==null){
System.out.println("Head is exist ,but the link data empty");
return true;
}else {
return false;
}
}
/**将线性表清空**/
public void ClearList(){
if(singleLink!=null){
singleLink.next=null;
singleLink=null;
}
}
/**返回链表中的第i个元素**/
public Student GetElem(int i){
int location=0;
if(singleLink!=null&&singleLink.next!=null){
Student temp=singleLink.next;
while(temp!=null){
location++;
if(location==i) {
return temp;
}else{
temp=temp.next;
}
}
}
return null;
}
/**返回student在单链表中的位置**/
public int LocateElem(Student student){
int location=0;
if(singleLink!=null&&singleLink.next!=null){
Student temp=singleLink.next;
while(temp!=null){
location++;
if(temp.name.equals(student.name)&&temp.num.equals(student.num)) {
return location;
}else{
temp=temp.next;
}
}
}
return location;
}
/**在第i个位置插入新元素 student**/
public void ListInsert(int i,Student student){
int location=0;
if(singleLink!=null){
Student previous=singleLink.next;
while(location!=i){
location++;
if(previous!=null&&previous.next!=null){
previous=previous.next;
}
}
if(previous!=null){
Student next=previous.next;
previous.next=student;
student.next=next;
}else{
singleLink.next=student;
student.next=null;
}
}
}
/**删除第i个位置上的元素**/
public Student ListDelete(int i){
i--;
int location=0;
Student delete=null;
if(singleLink!=null&&singleLink.next!=null){
Student temp=singleLink;
while(temp!=null){
if(location==i) {
break;
}else{
location++;
temp=temp.next;
}
}
delete=temp;
temp.next=temp.next.next;
}
return delete;
}
/**返回单链表的元素个数**/
public int ListLength(){
int location=0;
if(singleLink!=null&&singleLink.next!=null){
Student temp=singleLink.next;
while(temp!=null){
location++;
temp=temp.next;
}
}
return location;
}
}
class Student {
String name;
String num;
Student next;
public Student(){}
public Student(String name, String num) {
super();
this.name = name;
this.num = num;
}
}