深入线性表(链表)
一,线性表定义
线性表(List):零个或多个数据元素的有限序列。
有限:0个或者多个数据元素(只有数学概念上才存在无限)
序列:数据元素都是有序的。
元素类型:相同,每个数据元素的类型是相同的。
若将线性表记为(a1, … , ai-1, ai, ai+1, … , an),则表中 ai-1 领先于 ai,ai+1 领先于 ai,称 ai-1 是 ai 的直接前驱元素,ai+1 是 ai 的直接后继元素。当 i = 1,2, … , n-1 时,ai 有且仅有一个直接后继,当 i = 2,3, … , n 时,ai 有且仅有一个直接前驱。
抽象数据类型ADT
ADT 线性表(List)
Data
线性表的数据对象集合为{a1,a2,...,an},每个元素的类型均为 DataType。其中,除第一个元素 a1 外,每个元素有且只有一个直接前驱元素;除了最后一元素 an 之外,每一个元素有且只有一个直接后继元素。数据元素之间的关系是一对一的关系。
Operation
InitList(*L):初始化操作,建立一个空的线性表 L。
ListEmpty(*L):若线性表为空,返回 true,否则返回 false。
ClearList(*L):将线性表清空。
GetElem(*L,i,*e):将线性表中第 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
二,线性表的存储结构(顺序,链式)
1.顺序存储结构
顺序表:使用一组地址连续的存储单元依次存储数据元素
小编提示:
在编程语言C上不就是**数组**嘛,在java上不就是**ArrayList**嘛,用下标增删改查元素。
特点:
长度固定,必须在分配内存之前确定数组的长度。
存储空间连续,即允许元素的随机访问。
存储密度大,内存中存储的全部是数据元素。
要访问特定元素,可以使用索引访问,时间复杂度为O(1) 。
在顺序表中插入或删除一个元素,都涉及到之后所有元素的移动,因此时间复杂度为O(n)
(1)操作
首先我们要构造线性表
#define MAXSIZE 200
typeof int Elemtype;
typedef struct
{
ElemType data[MAXSIZE];/*创建数组存储数据元素*/
int length;/*线性表长度*/
}SqList;
这不就相当于数组+数组长度,所以说顺序存储就是数组。操作线性表,就相当于操作数组
获得元素
首先定义一下返回状态
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 1
typeof int Status;
操作开始:参数有线性表,位置,接收元素的指针
Status GetElem(SqlList L,int i,ElemType *e){
//首先应该想什么
/*当然是找特殊情况了
*如果线性表没元素,不能获得元素
*如果 i<1或者i>length,同样不能
*/
if(L.length<1||i<1||i>L.length){
return ERROR;
}
//特殊情况之后,就是正常操作了
*e=L.data[i-1];
return OK;
}
//O(1)
插入元素
参数e为要插入的元素
Status ListInsert(SqlList *L,int i,ElemType e){
//想什么? 对,特殊情况
/*L满了,i<1,i>length+1*/
if(L->length==MAXSIZE||i<1||i>L->length+1){
return ERROR;
}
//如果插入位置不是length+1,将第i个元素开始依次向后移动一位
if(i<=L->length){
for(int k = L->length-1;k>=i-1;k--){
L->data[k+1]=L->data[k];
}
}
L->length++;
L->data[i-1]=e;
return OK;
}
//除了在尾部插入O(1)以外,其他情况O(n)。
删除元素
(注意上边两个方法的参数L,一个是结构体,一个是结构体指针,结构体用. 指针用->)
Status ListDelete(SqlList *L,int i,ElemType *e){
/*length<1,i<1,i>length*/
if(L->length<1||i<1||i>L->length){
return ERROR;
}
*e=L->data[i-1];
if(i<L->length){
for(k=i;k<L->length;k++){
L->data[k-1]=L->data[k];
}
}
L->length--;
return OK;
}
//除了尾部删除都为O(n)
2.链式存储
首先,链表中一个结点包含有一个指针域,就叫单链表;如果包含两个就是双链表。
指向链表的指针叫做头指针head,链表为空:headNULL(沃兹基硕德)
为了链表好操作,在第一个结点之前加入一个头节点,指针域包含指向第一个节点的指针。
头指针指向头结点,空:head->nextNULL
定义指针结构体
typedef struct Node{
ElemType data;
struct Node *next;
}Node;//才有点理解C语言这个结构体为什么这样写,将 struct Node{...}起别名为Node
typedef struct Node *LinkList; //将struct Node再起别名为LinkList
/*
*在方法中创建结点就不用带struct了
*起别名,结构体具体咋回事我有空再看看C,时间长不用忘了
*/
获得第i个结点的数据
/*有头结点*/
Status GetElem(LinkList L,int i,ElemType *e){
//因为不知道L的长度,所以不能直接拿出特殊情况
if(L->next==NULL||i<1)return ERROR;
LinkList *p;
p=L->next;//第一个结点 不能直接用L=L->next;
while(p){//如果p没过最后一个结点。因为不知道循环几次用while
if(i==1){//走到这就说明找到了。
*e=p->data;
return OK;
}
//找下一个结点
p=p->next;
i--;
}
return ERROR;
}
/*
*总结一下:首先特殊情况
* 其次就是p不为空的while循环
*/
单链表插入
头插法(i=0)
Status ListInsert(LinkList *L,int i,ElemType e){
/*特殊情况*/
LinkList p,s;
p=*L;//头结点
/*寻找第i-1个元素,在它后面插入*/
while(p&&i-1>0){
p=p->next;
i--;
}
if(!p||i<1){//i<1说明插入位置不存在
return ERROR;
}
s=(LinkList)malloc(sizeof(Node))
s->data=e;
s->next=p->next;p->next=s;
return OK;
}
单链表删除
Status ListDelete(LinkList *L,int i,ElemType *e){
int j;//用书上的方法,其实一样
LinkList p,s;
p=*L;
j=1;
//找i-1
while(p&&j<i-1){
p=p->next;
j++;
}
if(!p||!(p->next)||j>i){//!p->next,第i为NULL,删除位置不对
return ERROR;
}
*e=p->next->data;
s=p->next;
p->next=p-next->next;
free(s);
return OK;
}
还有特殊的头插法,尾插法。我相信不过多介绍也能写出来。可以去测试一下自己。
return ERROR;
}
*e=p->next->data;
s=p->next;
p->next=p-next->next;
free(s);
return OK;
}
还有特殊的**头插法,尾插法**。我相信不过多介绍也能写出来。可以去测试一下自己。
[LeetCode707. 设计链表]: https://leetcode-cn.com/problems/design-linked-list/