线性表
线性表的顺序表示
顺序表的定义
初始化
以下是一个最简单的顺序表
#include <stdio.h>
#define MaxSize 10
typedef struct {
int data[MaxSize];
int length;
}SqList;
void InitList(SqList *L){
L->length=0;
for (int i = 0; i < MaxSize; ++i) {
L->data[i]=0;
}
}
int main() {
SqList L;
//InitList(&L);
//尝试“违规”打印整个data数组
for (int i = 0; i < MaxSize; ++i) {
printf("data[%d]=%d\n",i,L.data[i]);
}
return 0;
}
如果我们不对该表内的数据进行初始化,直接访问就会发现分配到的内存中有遗留的“脏数据”
但我们一般不会这样访问打印数组,应该是把MaxSize换为该顺序表目前的长度,如下:
更好的做法是使用基本操作来访问各个数据元素GetElem(L,i)
存储大小
在声明SqList L时,会在内存中分配存储顺序表L的空间。
包括:MaxSize * sizeof(ElemType) 和 存储length 的空间
动态分配
顺序表实现--动态分配
#include <stdlib.h>
#define InitSize 10
typedef struct {
int *data;//指示动态分配数组的指针
int Maxsize;
int length;//顺序表当前长度
}SqList;
//初始化
void InitList(SqList *L){
//用malloc函数申请一片连续的存储空间
L->data=(int *)malloc(sizeof(int)* InitSize);
L->length=0;
L->Maxsize=InitSize;
}
//增加动态数组的长度
void IncreaseSize(SqList *L,int len){
//声明一个临时指针指向原来内存位置的第一个元素
int *p= L->data;
//重新申请一片更大的内存位置(15),原指针指向新内存位置的首位
L->data= (int *) malloc(sizeof(int)*(L->Maxsize+len));
//从原来的内存位置取出数据放到新的内存位置上
for (int i = 0; i < L->length; ++i) {
L->data[i]=p[i];
}
L->Maxsize+=len;
//释放原来申请的内存(10)
free(p);
}
int main() {
SqList L;
InitList(&L);
IncreaseSize(&L,5);
return 0;
}
顺序表的操作
增删操作以及问题思考
#include <stdbool.h>
#include <stdio.h>
#define InitSize 10
typedef struct {
int data[InitSize];
int length;
} SqList;//静态数组
void InitList(SqList *L) {
L->length = 0;
}
//在指定位序插入元素
bool ListInsert(SqList *L, int i, int e) {
//表已满
if (L->length == InitSize) { return false; }
//i为位序,e为插入的元素
if (i < 1 || i > L->length + 1) {
return false;
}
for (int j = L->length; j >= i; --j) {
L->data[j] = L->data[j - 1];
}
L->data[i - 1] = e;
L->length++;
return true;
}
//删除指定为序的元素并返回所删元素
bool ListDelete(SqList *L, int i, int *e) {
//i为所删元素的位序,e为要删除的元素
if (i < 1 || i > L->length) { return false; }
*e = L->data[i - 1];
for (int j = i; j < L->length; ++j) {
L->data[j - 1] = L->data[j];
}
L->length--;
return true;
}
int main() {
SqList L;
//所删元素
int e = -1;
InitList(&L);
//IncreaseSize(&L, 5);
for (int i = 0; i < 10; ++i) {
ListInsert(&L, i + 1, i);
}
//测试是否插入成功
for (int i = 0; i < L.length; ++i) {
printf("data[%d] = %d\n", i, L.data[i]);
}
ListDelete(&L, 5, &e);
printf("DeletedElem = %d\n", e);
//测试是否删除成功
for (int i = 0; i < L.length; ++i) {
printf("data[%d] = %d\n", i, L.data[i]);
}
return 0;
}
在做插入操作时,最好先画图搞明白位序与索引的关系,在for语句中通过位序角度思考问题,而在for主体中,应该考虑索引与位序的关系。
要考虑代码的健壮性以及结构体中其他属性是否需要改变。
在做删除操作并返回所删元素时,出现指针问题。并报错
incompatible integer to pointer conversion assigning to 'int *' from 'int'; take the address with &
//删除指定为序的元素并返回所删元素
bool ListDelete(SqList *L,int i,int *e){
//i为所删元素的位序,e为要删除的元素
if(i<1||i>L->length){return false;}
e=&L->data[i-1];
for (int j = i; j <L->length ; ++j) {
L->data[j-1]=L->data[j];
}
L->length--;
return true;
}
这段代码中的错误是因为尝试将int
类型的值赋给int*
类型的指针变量。在这个函数中,e
是一个指向int
的指针,它应该存储一个地址而不是一个值。但在这行代码中:
e=&L->data[i-1];
e
被赋值为L->data[i-1]
的地址,这本身是正确的。问题可能在于e
的声明或者使用方式。如果e
被用来存储被删除元素的值,那么你应该直接赋值,而不是赋地址。正确的代码应该是:
*e=L->data[i-1];
这样,e
就会指向被删除元素的值,而不是它的地址,现在,e
将正确地存储被删除元素的值,而不是地址。
增删操作的时间复杂度
查找查找及其时间复杂度
//按位查找(获取表L中的第i个位置的元素的值)
int GetElem(SqList *L,int i){
if(i<1||i>L->length){return -1;}
return L->data[i-1];
}
//按值查找(在顺序表中查找第一个元素值为e的元素,并返回其位序)
int LocateElem(SqList *L,int e){
for (int i = 0; i <= L->length-1; ++i) {
if(e==L->data[i]){
return i+1;
}
}
return -1;
}
按位查找的时间复杂度为O(1)
按值查找的时间复杂度为O(n)
线性表的链式表示
什么是单链表
如果不使用typedef则会
typedef很常用
LinkList L等价于LNode *L,传参列表里使用Link List L强调传入的是单链表的头指针
不带头结点的单链表
不带头结点的缺点:
对第一个数据节点和后续数据节点的处理需要用不同的代码逻辑
对空表和非空表的处理需要用不同的代码逻辑
带头结点的单链表
#include <stdio.h>
#include <stdlib.h>
typedef struct LNode
{ // 定义单链表结点类型
int data; // 每个节点存放一个数据元素
struct LNode *next; // 指针指向下一个节点
} LNode,*LinkList;
// 等价于以下
// struct LNode{
// int data;
// struct LNode * next;
// }
// typedef struct LNode LNode;
// typedef struct LNode *LinkList;
// LNode * L等价于LinkList L 声明一个指向单链表第一个节点的指针,后者可读性更强
int InitLinkList(LinkList *L)
{
*L = (LNode *)malloc(sizeof(LNode)); // 分配一个头结点
if (L == NULL)
{
return 0;
} // 内存不足,分配失败
(*L)->next = NULL; // 头结点之后暂时还没有结点
return 1;
}
int Empty(LinkList *L)
{
return ((*L)->next == NULL);
}
int main()
{
LinkList L;
if (InitLinkList(&L))
{
printf("%p \n",L);
printf("LinkList initialized successfully.\n");
if(Empty(&L)){printf("empty");}
}
else
{
printf("Failed to initialize LinkList.\n");
}
return 0;
}
单链表的增删改查
//
// Created by Wai on 2024/5/13.
//
#include <stdio.h>
#include <malloc.h>
typedef struct LNode { // 定义单链表结点类型
int data; // 每个节点存放一个数据元素
struct LNode *next; // 指针指向下一个节点
} LNode, *LinkList;
int InitLinkList(LinkList *L) {
*L = (LNode *) malloc(sizeof(LNode)); // 分配一个头结点
if (*L == NULL) {
return 0;
} // 内存不足,分配失败
(*L)->next = NULL; // 头结点之后暂时还没有结点
return 1;
}
int Empty(LinkList *L) {
return ((*L)->next == NULL);
}
//按位查找:获取表L中第i个位置的节点
LNode *GetElem(LinkList *L, int i) {
if (i < 0) { return NULL; }
LNode *p = *L;
int index = 0;
while (p != NULL && index < i) {
p = p->next;
index++;
}
return p;
}
//按位序插入,在表L中的第i个位置插入指定元素e
int ListInsert(LinkList *L, int i, int e) {//LinkList *L为二级指针,需要*L降维到一级指针
if (i < 1) { return 0; }
//如果不带头结点,插入第一个节点的操作和别的不一样
// if (i == 1) {
// LNode *p = (LNode *) malloc(sizeof(LNode));
// p->next = *L;
// *L = p;//使头指针指向新节点
// p->data = e;
// return 1;
// }
LNode *p = *L;//指针p指向当前扫描到的结点,并将其初始化为头结点
int index = 0;//当前p所指向的第几个节点,如果不带头结点的话需要赋值为1
//循环找到第i-1个节点
// while (p != NULL && index < i - 1) {
// p = (*p).next;
// index++;
// }
p = GetElem(L,i-1);
if (p == NULL) {//index不合法(假设表长为4,把新元素插入到第六个位置)
return 0;
}
//使第i-1个元素的指针域赋值给s的指针域,然后指向s
LNode *s = (LNode *) malloc(sizeof(LNode));
(*s).data = e;
(*s).next = (*p).next;
(*p).next = s;
return 1;
}
//后插操作:在p节点之后插入元素e
int InsertNextNode(LNode *p, int e) {
if (p == NULL) { return 0; }
LNode *s = (LNode *) malloc(sizeof(LNode));
if (s == NULL) { return 0; }//内存分配失败
s->next = p->next;
p->next = s;
(*s).data = e;
return 1;
}
//前插操作:在p节点之前插入元素e
int InsertPriorNode(LNode *p, int e) {
if (p == NULL) { return 0; }
LNode *s = (LNode *) malloc(sizeof(LNode));
if (s == NULL) { return 0; }
//(在p后加入新节点s,然后p和s的内容进行调换)
// 使s指针域指向p下一个节点,然后把p的数据同步到s上,然后把p的数据改为新数据
s->next = p->next;
p->next = s;
(*s).data = (*p).data;
(*p).data = e;
return 1;
}
//按位序删除(带头,删除表L中第i个位置的元素,并用e返回删除元素的值)
int ListDelete(LinkList *L, int i, int *e) {
if (i < 1) { return 0; }
int index = 0;//位序变量
LNode *p = *L;//当前指针
// while (p != NULL && index < i - 1) {
// p = p->next;
// index++;
// }
p = GetElem(L,i-1);
if (p == NULL) { return 0; }
if (p->next == NULL) { return 0; }
*e = (*p).data;
LNode *temp = p->next;//设置临时指针指向那个准备删除的节点
p->next = temp->next;
free(temp);//变量为要被删除节点的地址
return 1;
}
//删除指定节点p(有坑:删除指定节点为最后一个节点时需要特殊处理)
int DeleteNode(LNode *p) {
if (p == NULL) { return 0; }
LNode *temp = p->next;
p->next = temp->next;
(*p).data = (*temp).data;
free(temp);
return 1;
}
//按值查找:在表L中查找具有给定关键字值的元素
LNode *LocateElem(LinkList *L, int e) {
LNode *p = *L;
//从第一个节点开始查找数据域为e的节点
while (p !=NULL && p->data!=e) {
p = p->next;
}
//找到后返回该结点指针,否则返回NULL
return p;
}
int main() {
LinkList L;
if (InitLinkList(&L)) {
printf("%p \n", L);
printf("LinkList initialized successfully.\n");
if (Empty(&L)) { printf("empty"); }
} else {
printf("false");
}
ListInsert(&L, 1, 8);
return 0;
}
双链表
#include <stdio.h>
#include <malloc.h>
#include <stdbool.h>
typedef struct DNode {//定义双链表结点类型(double)
int data;//数据域
struct DNode *prior, *next;//前驱和后继指针
} DNode, *DLinkList;
//初始化双链表
bool InitDLinkList(DLinkList *L) {
*L = (DNode *) malloc(sizeof(DNode));
if (*L == NULL) { return false; }
(*L)->prior = NULL;
(*L)->next = NULL;
(*L)->data = 0;
return true;
}
//判断表是否为空
bool Empty(DLinkList *L) {
if ((*L)->next == NULL) { return true; }
return false;
}
//在p结点之后插入s结点
bool InsertNextDNode(DNode *p, DNode *s) {
//处理非法参数
if (p == NULL || s == NULL) { return false; }
//先同步s
s->next = p->next;
s->prior = p;
//建立p与s的连接
p->next = s;
//如果p不为最后一个结点
if (s->next != NULL) { s->next->prior = s; }
return true;
}
//删除p结点的后继结点
bool DeleteNextDNode(DNode *p) {
//处理非法参数
if (p == NULL) { return false; }
//如果p不为最后一个结点
if (p->next != NULL) {
p->next = p->next->next;
free(p->next);
p->next->prior = p;
return true;
}
return false;
}
//销毁双链表
bool DestroyList(DLinkList *L) {
//循环释放各个数据结点
while ((*L)->next != NULL) {
DeleteNextDNode(*L);
}
free(*L);
return true;
}
//后向遍历
void TraverseNext(DNode *p) {
while (p != NULL) {
//do something
p = p->next;
}
}
//前向遍历
void TraversePrior(DNode *p) {
//跳过头结点(p->prior != NULL)
while (p != NULL) {
//do something
p = p->prior;
}
}
int main() {
DLinkList L;
InitDLinkList(&L);
return 0;
}
循环单链表
可以把链表指针L指向表尾结点
好处是:修改表头和表尾结点时的事件复杂度为O(1)
坏处是:在表尾新增或删除结点需要移动指针L
#include <stdio.h>
#include <malloc.h>
#include<stdbool.h>
typedef struct LNode {
int data;
struct LNode *next;
} LNode, *LinkList;
bool InitLinkList(LinkList *L) {
*L = (LNode *) malloc(sizeof(LNode));
if (*L == NULL) { return false; }
(*L)->next = *L;//头结点next指向头结点
return true;
}
bool Empty(LinkList *L) {
return ((*L)->next == *L);
}
//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList *L, LNode *p) {
if (p == NULL) { return false; }
return (p->next == *L);
}
循环双链表
#include <stdio.h>
#include <malloc.h>
#include<stdbool.h>
typedef struct DNode {//定义双链表结点类型(double)
int data;//数据域
struct DNode *prior, *next;//前驱和后继指针
} DNode, *DLinkList;
bool InitLinkList(DLinkList *L) {
*L = (DNode *) malloc(sizeof(DNode));
if (*L == NULL) { return false; }
(*L)->next = *L;
(*L)->prior = *L;
return true;
}
bool Empty(DLinkList *L) {
return ((*L)->next == *L && (*L)->prior == *L);
}
//判断结点p是否为循环单链表的表尾结点
bool isTail(DLinkList *L, DNode *p) {
if (p == NULL) { return false; }
return (p->next == *L);
}
//在p结点之后插入s结点
bool InsertNextNode(DNode *p, DNode *s) {
s->next = p->next;
s->prior = p;
p->next = s;
s->next->prior = s;
return true;
}
//删除结点p
bool DeleteNextNode(DNode *p) {
if (p == NULL) { return false; }
DNode *q=p->next;
DNode *o=p->prior;
o->next=q;
q->prior=o;
free(p);
}
静态链表
#include <stdio.h>
#include <malloc.h>
#include<stdbool.h>
#define MAXSIZE 10
//声明静态链表
typedef struct {
int data;
int next;//下一个元素的数组下标
} SLinkList[MAXSIZE];
void test() {
SLinkList a;//是一个静态链表
//等价于struct Node a[MAXSIZE];
}