数据结构基础代码讲解 线性表篇(C)

线性表

线性表的顺序表示

顺序表的定义

初始化

以下是一个最简单的顺序表 

#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];
}

猜你喜欢

转载自blog.csdn.net/weixin_74163644/article/details/138129218