什么是线性表?
所要处理的元素在逻辑上是线性的,即除去头尾元素,其他的元素都有且只有一个直接前驱和后继。
物理结构:顺序结构、链式结构
顺序存储结构
存储结构
typedef int ElemType; //不同需求下,ElemType可取不同类型
typedef struct SqList{
ElemType *elem; //elem[MAXSIZE]就定长了
int length; //当前长度
int listsize; //目前最大长度,
//因为是动态分配的数组大小,当表的长度不够时可以通过realloc函数增长
}SqList;
运算(初始化、增、删、改、查)
- 初始化
const int INIT_SIZE = 100; //初始化长度
const int INCREAMENT_SIZE = 100; //随着表长增加,表不够长时的增长量
void InitList(SqList *L){ //这里用指针否则只修改形参
L->elem = (ElemType*)malloc(INIT_SIZE*sizeof(ElemType));
L->length = 0;
L->listsize = INIT_SIZE;
}
- 增(插入), O ( n ) O(n) O(n)
void Insert(SqList *L,ELemType e,int index){
//将元素插入到index处,index > -1
if(index < 0 || index > L->length) //越界检查
PrintError(),return;
//将第index个以及往后的所有元素右移
for(int i = L->length - 1; i >= index; i--)
L->elem[i+1] = L->elem[i];
L->index = e; //插入
L->length ++; //长度记得+1
/*这里可以加一个长度不够的处理逻辑
if(check(*L)){
L->elem = realloc(L->elem,sizeof(ELemType)*(L->listsize + INCREAMENT_SIZE));
L->length ++;
L->listsize += INCREAMENT_SIZE;
}
*/
}
- 删(增的逆运算), O ( n ) O(n) O(n)
void Delete(SqList *L,ElemType &e,int index){//e是引用变量,带回删除的元素
//删除index位置的元素
if(index < 0 || index > L->length - 1) //越界检查
PrintError(),return;
e = L->elem[index];
//将第index个元素后面的所有元素左移一个单位
for(int i = index; i < L->length; i++)
L->elem[i] = L->elem[i+1];
L->length --; //长度记得-1
}
- 改和查 ,
O
(
1
)
O(1)
O(1)
没必要实现,利用顺序存储的性质,直接用[ ]访问就可以。 - 其他典型操作
//删除顺序表中与x相同的元素 O(n)
void DeleteX(SqList *L,ElemType x){
for(int i = 0,k = 0; i < L->length; i++){
if(L->elem[i] == x)
continue;
else
L->elem[k++] = L->elem[i];
}
L->length = k;
}
链式存储结构(单链表)
逻辑上连续,物理上不连续。一条链,故称其为单链。
存储结构
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
运算(初始化,创建、增、删、改、查)
- 初始化(创建一个头结点)
void InitList(LinkList &L){ //用引用,不怕麻烦就用指针
L = (ElemType)malloc(sizeof(LNode));
L->data = 0; //存放表长或者啥的,随你的便,无所谓
L->next = NULL;
}
- 创建(顺序表循环赋值就行了)
void CreateList_H(LinkList &L,int n){ //头插创建一个表长为n的链表
//此时假设已经调用过InitList();
LNode *p;
while(n--){
p = (ElemType*)malloc(sizeof(LNode));
scanf("%d",&p->data);
p->next = L->next;
L->next = p;
}
}
void CreateList_T(LinkList &L,int n){ //尾插创建一个表长为n的链表
//此时假设已经调用过InitList();
LNode *p,*t = L;
while(n--){
p = (ElemType*)malloc(sizeof(LNode));
scanf("%d",&p->data);
t->next = p;
t = p;
}
t->next = NULL; //尾指针记得置空
}
- 增(插入)
O ( i n d e x ) O(index) O(index)
void Insert(LinkList &L,ElemType e,int index){ //只要你不修改头结点,其实这里L可以不用引用的,因为操作的是头结点的指针,无所谓了......
//这里index从1开始,顺序表从0,插入到第index个位置
if(index > getLength(L) || index < 0)
PrintError(),return;
//理所当然的拿到index-1位置的指针
int cnt = 0;
LNode *p = L;
while(cnt < index - 1){
p = p->next;
cnt ++;
} //退出时cnt == index - 1
LNode *t = (ElemType*)malloc(sizeof(LNode));
t->data = e;
t->next = p->next;
p->next = t;
}
- 删(跟上面类似的思路,拿到前驱指针,退出循环后的处理操作不一样罢了)
O ( i n d e x ) O(index) O(index)
void Insert(LinkList &L,ElemType e,int index){ //只要你不修改头结点,其实这里L可以不用引用的,因为操作的是头结点的指针,无所谓了......
//这里index从1开始,顺序表从0,插入到第index个位置
if(index > getLength(L) || index < 0)
PrintError(),return;
//理所当然的拿到index-1位置的指针
int cnt = 0;
LNode *p = L;
while(cnt < index - 1){
p = p->next;
cnt ++;
} //退出时cnt == index - 1
LNode *t = p->next;
p->next = t->next;
free(t);
}
- 改和查也跟上面差不多,没啥意思
其他链表
- 循环链表
把尾指针指向头结点就行了,(首元结点也行随你的便)。通常只设一个尾指针,便于在表尾的插入,又因为尾指针指向了头结点,从头遍历也方便。 - 双向链表
顾名思义双向的,每一个结点既有前驱指针pre又有后继结点next。 - 双向循环链表
前两者结合得到。把双向链表的头结点的前驱指向表尾,同时把表尾的后继指向表头。
总结对比
顺序表:表长相对固定,插入删除需要移动元素,但存取迅速。
链表:表长自由,插入删除操作
O
(
1
)
O(1)
O(1),只需要找到前驱即可,缺点是存取不方便,需要从头扫。