前言
线性表包括顺序表
和链表
两种结构,顺序表类似于数组
,链表是除了类似数组之外的还包括指针
的概念,利用指针的指向来确定表的走向。本片文章我们将从顺序表和单向链表的实现及相关功能着手,学习线性表的特性,下篇文章我们会介绍剩余几种链表。(代码用的是C语言编写)
顺序表
顺序表
是在计算机内存中以数组
的形式保存的线性表,是将表中的结点依次存放在计算机内存中一组地址连续的存储单元中。
#include <stdio.h>
#include "stdlib.h"
#define MAXSIZE 100
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
//元素类型假设为int
typedef int ElemType;
//函数返回类型 如上的状态码,OK 等
typedef int Status;
//顺序表结构数据元素
typedef struct {
ElemType *data;
int length;
}Sqlist;
//1.1 初始化
Status InitList(Sqlist *L){//如果内部需要修改L,带*,不修改可直接带L
//分配一个大小为 MAXSIZE 的数组空间
L->data = malloc(sizeof(ElemType)*MAXSIZE);
if(!L->data) exit(ERROR);//存储分配失败就退出
L->length = 0;
return OK;
}
//1.2 插入数据
//i 第i个位置插入数据元素e,L长度加1
Status ListInsert(Sqlist *L, int i,ElemType e){
//i 不合法
if((i < 1) || (i > L->length + 1)) return ERROR;
//存储已满
if(L->length == MAXSIZE) return ERROR;
//插入位置不在表尾,则先移动出空余位置,就是将第i位置及后面的都往后移动一个位置
if(i <= L->length) {
for (int j = L->length - 1; j >= i - 1; j --) {
L->data[j + 1] = L->data[j];
}
}
//第i个位置插入
L->data[i - 1] = e;
++L->length;
return OK;
}
//1.3 取值
//*e 为装载取到的值,供外界使用
Status GetElem(Sqlist L,int i,ElemType *e){
//i 不合法
if((i < 1) || (i > L.length)) return ERROR;
*e = L.data[i - 1];
return OK;
}
//1.4 删除元素
Status listDelete(Sqlist *L, int i){
if(L->length == 0) return ERROR;
if(i < 1 || i > L->length) return ERROR;
for (int j = i; j < L->length; j ++) {
L->data[j - 1] = L->data[j];
}
L->length--;
return OK;
}
//1.5 清空
Status CLearList(Sqlist *L){
L->length = 0;
return OK;
}
//1.6 是否为空
Status ListEmpty(Sqlist L){
if(L.length == 0){
return TRUE;
}else{
return FALSE;
}
}
//1.7 长度
int ListLength(Sqlist L){
return L.length;
}
//1.8 遍历
Status PrintList(Sqlist L){
int i;
for (i = 0; i < L.length; i ++) {
printf("%d\n",L.data[i]);
}
printf("\n");
return OK;
}
//1.9 寻找下标
int LocateElem(Sqlist L, ElemType e){
int i;
if(L.length == 0) return 0;
for (i = 0; i < L.length; i ++) {
if(L.data[i] == e)
break;
}
if(i >= L.length) return 0;
return i + 1;
}
int main(int argc, const char * argv[]) {
Sqlist L;
Status iStatus;
iStatus = InitList(&L);
printf("初始化L后: L.Length = %d\n", L.length);
for(int j=1; j <= 5;j++){
iStatus = ListInsert(&L, 1, j);
}
printf("插入数据L长度: %d\n",L.length);
PrintList(L);
return 0;
}
需要注意的是插入和删除时length长度需要做相应的加1减1操作。其余数据需要移动位置。
单向链表
单向链表
是由若干个结点组成的链表结构,每个结点分数据域
和指针域
,数据域存放实际数据,指针域存放下个结点的地址。

链表第一个实际装有数据的结点称为首元结点
,首指针指向此结点,如果需要使用到头结点
,我们会给它加上一个头结点,头指针指向头结点,头结点数据域可以写有实际意思的数据,也可以置空,指针域存放首元结点的地址。日常开发中我们更倾向于加上头结点,原因如下:
1.便于首元结点的操作处理。
2.便于空表和非空表的统一处理。



准备工作
#include <stdio.h>
#include "stdlib.h"
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int Status;
typedef int ElemType;
//定义结点
typedef struct Node{
ElemType data;//数据域
struct Node *next;//指针域
}Node;
typedef struct Node *LinkList;
初始化
此处我们添加了头结点
,初始化时候为空表,只有一个头结点,所以next->NULL
。
//1.1 初始化
Status InitList(LinkList *L) {
//头结点
*L = (LinkList)malloc(sizeof(Node));
if(*L == NULL) return ERROR;
//将头节点的指针域置空
(*L)->next = NULL;
return OK;
}
插入数据
假设需要在两个数据元素a和b之间插入一个数据元素x,即打破a和b之间的关系,需求a->next = x,x->next = b
,需要注意的是要先将x->next指向b,然后再a->next指向x,如果顺序反了的话,b会被释放掉。

Status ListInsert(LinkList *L, int i, ElemType e){
int j;
LinkList p,s;
p = *L;
j = 1;
//寻找第i - 1个结点
while (p && j < i) {
p = p->next;
++j;
}
if(!p || j > i) return ERROR;
s = (LinkList)malloc(sizeof(Node));
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
获取数据(取值)
获取第i个数据元素的值。那就找到第i - 1个结点的next结点。
Status GetElem(LinkList L, int i, ElemType *e){
int j;
LinkList p;
//将结点p 指向链表L的第一个结点;
p = L->next;
j = 1;
while (p && j < i) {
p = p->next;
++j;
}
if(!p || j > i) return ERROR;
*e = p->data;
return OK;
}
数据删除
删除结点操作就是插入操作的逆序,假设a->b->c,需要将b删除,那么就需要将a->next = c
就行了。注意要释放b
的内存。

//删除第i个位置,即找到前一个和后一个,将它们连上关系
Status ListDelete(LinkList *L,int i, ElemType *e){
int j;
LinkList p,q;
p = (*L)->next;
j = 1;
while (p->next && j < (i - 1)) {
p = p->next;
++j;//最终 j = i - 1
}
//此时找到第i-1个数据,即要删除的前一个结点,需要用到它的next
//此时p->next就是需要删除的第i个数据元素
//此时j = i - 1
if (!(p->next) || (j > i - 1)) return ERROR;
//q指向要删除的结点
q = p->next;
//将q的后继赋值给p的后继
p->next = q->next;
//获取q中的数据给e
*e = q->data;
//释放内存
free(q);
return OK;
}
遍历链表
顺序表可根据下标
遍历,链表可根据结点的指针域next
遍历。
Status ListPrint(LinkList L){
LinkList p = L->next;
while (p) {
printf("%d\n",p->data);
p = p->next;
}
printf("\n");
return OK;
}
链表清空
注意清空时候需要释放开辟的内存。
Status ClearList(LinkList *L){
LinkList p,q;
p = (*L)->next;
while (p) {
q = p->next;
free(p);//从首元结点开始释放
p = q;
}
(*L)->next = NULL;//头结点指针域置空
return OK;
}
建立链表(头插法/尾插法)
建立一个带头结点的链表可以使用头插法
和尾插法
两种实现方式。
//头插法
void CreateListHead(LinkList *L, int n){
LinkList p;
//先建立带头结点的单链表
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;
for (int i = 0; i < n; i ++) {
p = (LinkList)malloc(sizeof(Node));
p->data = i;
//将头结点next给到p的next,空表是即NULL,非空时为首元结点
p->next = (*L)->next;
//将p插入为首元结点
(*L)->next = p;
}
}
//尾插法
void CreateListTail(LinkList *L,int n){
LinkList p,r;
*L = (LinkList)malloc(sizeof(Node));
r = *L;//此时为头结点
for (int i = 0; i < n ; i ++) {
p = (Node *)malloc(sizeof(Node));
p->data = i;
r->next = p;//表尾指针指向新结点
r = p;//依次为新加入结点,即尾结点
}
r->next = NULL;//最后尾结点next指向NULL
}
顺序表/单向链表对比
存储分配方式
:
- 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素。
- 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素。
时间性能
:
-
查找数据
- 顺序存储 O(1)
- 单链表 O(n)
-
插入和删除
- 顺序存储结构需要平均移动一个表长一半的元素,时间为O(n)
- 单链表查找某位置后的指针后,插入和删除,时间为O(1)
空间性能
:
- 顺序存储结构需要预先分配存储结构,分太大,浪费空间,分太小,发生溢出。
- 单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制。
在实际开发中,视情况而定,那种存储方式方便用哪个,实际问题实际分析。
网友评论