线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列,其中n为表长,当n=0时,线性表是一个空表。若用L来命名线性表,则一般表示为:
其中:
实现顺序表的方式既可以是静态的也可以是动态的。静态分配指的是线性表的大小和空间已经确定好,一旦空间占满,再加入新数据会导致溢出,进而程序崩溃;动态分配则是指存储空间是在程序执行期间通过动态存储分配语句分配的,一旦空间占满,就另外开辟一块更大的存储空间,用以替换原来的存储空间。
静态分配
#define MaxSize 10
typdef struct{
ElemType data[MaxSize];
int length;
}SqList1;
------------------------------------------------------ ----------------------------------------------------------
动态分配
typdef struct{
ElemType *data;
int length,MaxSize;
}SqList2;
// C的初始动态分配语句
L.data = (ElemType *)malloc(sizeof(ElemType)*InitSize);
// C++的初始动态分配语句
L.data = new ElemType[InitSize];
bool ListInsert(SqList &L,int i,ElemType e) {
if(i<1 || i>L.length+1)//插入位置不合法,不可以插到表头元素的上一个位置、表尾元素的下一个位置
return false;
if(L.length >= MaxSize)//元素个数不可以超过最大存储个数
return false;
for(int j = L.length; j >= i; j--){// 从表尾到指定位序的元素依次向后移动一个位置
L.data[j] = L.data[j-1];
}
data[i-1] = e;// 将e赋值给第i个位置(数组里对应i-1)
L.length++;
return true;
}
bool ListDelete(Sqlist &L,i,ElemType &e){
if(i<1 || i>L.length+1)//删除位置不合法,不可以删除不存在的位置
return false;
e = L.data[i-1];// 保存要删除的元素
for(int j = i; j <= L.length-1; j++ ){// 从要删除的元素的下一个位置开始到表尾元素依次向前移动一个位置
L.data[j-1] = L.data[j];
}
L.length--;
return true;
}
int LocateElem(SqList L,ElemType e){
int i;
for(i = 0; i < L.length; i++){// 遍历顺序表寻找第一个值==e的元素
if(L.data[i] == e) {
return i+1;// 位序 == 下标 + 1
}
}
return 0;
}
int GetElem(SqList L,int i) {
if(i < 1 || i > MaxSize){
return 0;
}
return L.data[i-1];
}
线性表的链式存储叫做单链表,指通过一组任意的存储单元来存储线性表中的数据元素。
与顺序表相比,单链表的优点是不用占用大片连续的空间,比较灵活,同时扩展新空间也比较方便;但是由于为了建立数据元素之间的线性关系,链表的每个结点除了要存储自身信息以外还要存储一个指向其后继的指针,这样的话,存储密度低同时链表就不支持随机存取了,需要通过指针一个一个的去查找。
代码描述如下:
typedef struct LNode {
ElemType data;
struct LNode *next;
}LNode, *LinkList;
为了操作方便,我们在单链表第一个结点之前附加一个结点,该节点不设置任何信息,它的指针域指向线性表的第一个元素,该节点称为头节点。
这样设置怎就方便了呢?
L->next == null;// 有头节点
L == null;// 没有附加头节点
LinkList List_HeadInsert(LinkList &L) {
LNode *s;
int x;
// 初始化头结点
L = (LinkList)malloc(sizeof(LNode));
L->next = 0;
// 输入数字 为9999时 终止输入
scanf("%d",&x);
while(x != 9999) {
// 初始化新结点
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
s->next = L->next;// 头结点指向的位置赋值给新结点的指针,头结点的指针域信息不可以丢失,否则新节点就链接不上其他的结点了
L->next = s;// 再将头结点的指针指向新结点
scanf("%d",&x);
}
return L;
}
LinkList List_TailInsert(LinkList &L) {
int x;
L = (LinkList)malloc(sizeof(LNode));
LNode *s,*r = L;// s指向新结点、r指向尾结点(r初始指向头结点)
scanf("%d",&x);
while(x != 9999) {
// 初始化新结点
s = (LNode *)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s; // r指向新的表尾结点
scanf("%d",&x);
}
r->next = NULL;// 表尾结点置空
return L;
}
LNode *GetElem(LinkList L,int i) {
if(i == 0) {// 返回头结点
return L;
}
if(i < 0) { // 输入不合法
return L;
}
int j = 1; // 计数
LNode *p = L->next; // 初始化移动指针
while(p && j < i) {// 从第一个结点开始寻找,查找第i个结点
p = p->next;
j++;
}
return p;
}
LNode *LocateElem(LinkList L,ElemType e) {
LNode *p = L->next;
while(p != null&&p->data != e) {
p = p->next;
}
return p;
}
// 按照位序插入:先检查i的合法性,再找到第i个的**前驱结点**,最后进行插入
bool ListInsert(LinkList &L,int i,ElemType e) {
if(i < 1) {
return false;// i不合法
}
LNode *p = L->next;
int j = 0;
while(p != null&&j < i-1) {// 找到第i个结点的前驱节点
p = p->next;
}
if(p == null) {
return false;// i不合法
}
// 创建新节点
LNode *s = (LNode*)malloc(sizeof(LNode));
s->data = e;
// 将新结点插入在第i-1个结点后面
s->next = p->next;
p->next = s;
return true;
}
// 已知道待插入结点和插入位置
bool InsertPriorNode(LNode *p,LNode *s) {
if(p == Null || s == Null)
return false;
// 待插入结点连接到链表
s->next = p->next;
p=>next = s;
// 偷梁换柱,将插入位置的结点里面的data与待插入的data交换
ElemType temp = s->data;
s->data = p->data;
p->data = temp;
return true;
}
// 按位序删除:检查删除结点合法性,找到被删除元素的前驱结点,将其删除
bool ListDelete(LinkList &L, int i, ElemType &e) {
if(i<1)
return false;// 删除位置不合法
LNode *p;// 移动指针
int j = 0;// 表示指针p指向第几个结点
p = L;
while(p != null && j<i-1) {// 找到前驱结点
p = p->next;
j++;
}
if(p==Null)
return false;// 删除位置不合法
LNode *q = p->next;// q指向被删除结点
e = q->data;// 保存即将被删除的数据
p->next = q->next;// 将前驱节点与即将删除的结点的下一个结点链接
free(q);// 删除
return true;
}
// 指定结点删除
bool DeleteNode(LNode *P) {
if(p == null)
return false;
LNode *q = p->next; // q指针指向p的后继结点
p->data = p->next->data;// 和后继结点交换数据域:当p为表尾结点时出现bug
p->next = q->next;// 将*q结点从链中释放
free(q);
return true;
}
int length(LinkList L) {
if(L->next == null) {
return 0;// 空表
}
LNode* p = L->next;
int j = 1;// 表长不包括头结点
while(p!=null) {
j++;
}
return j;
}
单链表只能向后遍历,而双链表可以双向遍历,为了支持双向遍历,双链表拥有两个指针分别指向其前驱与后继结点,代码实现如下:
typedef struct DNode {
ElemType data;
struct DNode *prior,*next;
}DNode, *DLinkList;
bool InitDLinkList(DLinkList &L){
L =(DNode*)malloc(sizeof(DNode));// 分配头结点
if(L==NULL) // 内存不足,分配失败
return false;
L->prior = null;// 头结点的prior永远指向null
L->next = null;// 头结点之后暂时没有结点
return true;
}
bool InsertNextDNode(DNode *p,DNode *s) {
if(p == null || s == null) {// 非法参数
return false;
}
s->next = p->next;
if(p->next != null) {// 如果p结点有后继结点
p->next->prior = s;
}
s->prior = p;
p->next = s;
return true;
}
// 删除p结点的后继结点
bool DeleteNextDNode(DNode *p) {
if(p==NUll) return false;// 不合法
DNode *q = p->next;
if(q == null) return false;// p没有后继
p->next = q->next;
if(q->next != null) {// q结点不是最后一个结点
q->next->prior = p;
}
free(q);
return true;
}
typedef struct LNode {
ElemType data;
struct LNode *next;
}LNode,*LinkList;
bool InitList(LinkList &L) {
L = (LNode*) malloc(sizeof(LNode));
if(L == Null) {// 分配内存失败
return false;
}
L->next = L;// 初始状态:头结点的指针指向自己
return true;
}
bool Empty(LinkList L) {
if(L->next == L) {
return true;
}else {
return false;
}
}
bool isTail(LinkList L,LNode *p) {
if(p->next == L)// 表尾元素的指针指向头结点
return true;
else
return false;
}
// 按照位序插入:先检查i的合法性,再找到第i个的**前驱结点**,最后进行插入
bool ListInsert(LinkList &L, int i, ElemType e) {
if (L == Null || i < 0 || i > L.length+1) return false; // 本身为空链表表或如果插入位置超出链表范围,返回 false
LNode *s = (LNode*)malloc(sizeof(LNode)); // 创建新节点
s->data = e;
LNode *p = L;// 移动指针
int j = 0;// 记录移动指针p经过第几个结点
LNode *q = L;// 初始化尾指针
while (q->next != L) { // 找到尾节点 q
q = q->next;
}
if (i == 0) { // 插入到链表头部
q->next = s;
s->next = p;
} else if (i == L.length+1) { // 插入到链表末尾
s->next = p->next;
p->next = s;
} else { // 插入到其他位置
while (j != i-1) { // 找到第 i-1 个节点
p = p->next;
j++;
}
s->next = p->next;
p->next = s;
}
return true;
}
bool ListDelete(LinkList &L, int i) {
if (i < 0 || i >= L->length)
return false;
LNode *p = L; // p 指向待删除节点的前驱节点
int j = 0;
while (j < i) { // 找到待删除位置的前一个节点
p = p->next;
j++;
}
LNode *q = p->next; // 待删除节点
if (q == L->next) // 如果待删除节点是头节点
L->next = q->next;
if (q == L) // 如果待删除节点是尾节点
L = p;
p->next = q->next; // 将待删除节点的前驱节点指向待删除节点的后继节点
if (L->next == L) // 如果链表中只有一个节点
L->next = nullptr;
free(q); // 释放待删除节点的内存
L->length--; // 更新链表长度
return true;
}
bool InitDLinkList(DLinkList &L) {
L = (DNode *)malloc(sizeof(DNode);
if(L == Null)
return false;
L->prior = L;
L->next = L;
return true;
}
bool Empty(DLinkList L) {
if(L->next == L) {
return true;
}else {
return false;
}
}
bool isTail(DLinkList L,LNode *p) {
if(p->next == L)// 表尾元素的指针指向头结点
return true;
else
return false;
}
静态链表借助数组来表述线性表的链式存储结构,需要提前分配一批连续的内存空间,这里的指针指的是结点的相对地址(数组下标),又称游标。游标为-1时表示已经到达表尾。
#define Maxsize 50
typdef Node {
ElemType data;
int next;// 下个数组的下标
};
typedef struct Node SlinkList{MaxSize};
int LocateElem(SLinkList L, ElemType elem) {
int index = L.r[0].next; // 从第一个节点开始查找
while (index && L.r[index].data != elem) {
index = L.r[index].next;
}
return index; // 返回查找到的节点下标,如果未找到则返回0
}
void ListInsert(SLinkList *L, int i, ElemType elem) {
if (i < 1 || i > L->length + 1) { // 越界检查
printf("插入位置不合法\n");
return;
}
if (L->length >= MaxSize - 1) { // 静态链表已满
printf("静态链表已满,无法插入\n");
return;
}
int index = 0;
for (int j = 1; j <= L->length; j++) {
if (j == i) {
// 在第i个位置插入新节点
index = j;
break;
}
if (L->r[j].next == 0) {
// 在链表末尾插入新节点
index = L->length + 1;
break;
}
}
if (index != 0) {
// 将新节点插入链表
L->r[index].data = elem;
L->r[index].next = L->r[0].next;
L->r[0].next = index;
L->length++;
printf("成功在第%d个位置插入新节点,元素值为%d\n", index, elem);
} else {
printf("插入失败\n");
}
}
void ListDelete(SLinkList *L, int i) {
if (i < 1 || i > L->length) { // 越界检查
printf("删除位置不合法\n");
return;
}
int index = (i == 1) ? L->r[0].next : 1;
int preIndex = 0;
for (int j = 1; j <= i; j++) {
if (j == i) {
// 找到待删除节点,调整连接关系
L->r[preIndex].next = L->r[index].next;
L->length--;
printf("成功删除第%d个位置的节点,元素值为%d\n", i, L->r[index].data);
return;
}
if (index == 0) {
// 链表遍历结束仍未找到待删除位置,删除失败
printf("删除失败\n");
return;
}
preIndex = index;
index = L->r[index].next;
}
}