数据结构2.0 线性表

一、线性表的定义和基本操作

①线性表的定义

②线性表的基本操作

③小结

二、顺序表的定义

①顺序表的定义

②顺序表的实现------静态分配

例:

③顺序表的实现------动态分配

例:

cpp 复制代码
// 为顺序表的动态数组申请内存:
// InitSize是默认最大长度,sizeof(int)是单个int元素的字节数,两者相乘得到总申请字节数
// (int *)是将malloc返回的void*指针强制转换为int*类型,适配data的指针类型
L.data = (int *)malloc (InitSize * sizeof(int));

// 为顺序表的动态数组扩容:
// L.MaxSize是当前最大容量,len是要增加的长度,两者相加是扩容后的总容量
// 用malloc申请新的连续内存空间,大小为"新总容量 × 单个int字节数"
// 同样将void*强制转为int*,赋值给L.data以指向新内存区域
L.data = (int *)malloc ((L.MaxSize + len) * sizeof(int));

④小结

三、顺序表的插入删除

①顺序表插入

②顺序表插入的健壮性

③顺序表插入的时间复杂度

④顺序表删除

⑤顺序表删除的时间复杂度

⑥小结

四、顺序表的查找

①按位查找

②按位查找的时间复杂度

③按值查找

cpp 复制代码
//结构类型数据元素

// 定义结构类型(学生:学号+姓名)
typedef struct {
    int id;       // 学号
    char name[20];// 姓名
} Student;

// 顺序表的结构(数据元素是Student类型)
typedef struct {
    Student *data;   // 动态数组指针
    int MaxSize;     // 最大容量
    int length;      // 当前长度
} SeqList;

// 在顺序表L中,查找第一个与e(Student类型)匹配的元素,返回位序(无则返回0)
int LocateElem(SeqList L, Student e) {
    for (int i = 0; i < L.length; i++) {
        // 结构类型:需逐个比较成员(学号+姓名都相同,才认为元素相等)
        if (L.data[i].id == e.id && strcmp(L.data[i].name, e.name) == 0) {
            return i + 1; // 返回位序(从1开始)
        }
    }
    return 0; // 未找到
}

④按值查找的时间复杂度

⑤小结

五、单链表的定义

①用代码定义一个单链表

cpp 复制代码
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode,*LinkList;
  • typedef struct LNode:给一个叫struct LNode的 "结构体" 起个别名(方便后面用)。
  • struct LNode里包含 2 个东西:
    • ElemType data:存节点的数据(比如数字、字符,ElemType是个 "占位符",实际用的时候会换成具体类型,比如int);
    • struct LNode *next:定义一个指针变量,变量名是 next,指向下一个节点(这样多个节点就能像链条一样连起来)。
  • }LNode, *LinkList;
    • struct LNode的别名定为LNode(以后写LNode就等于写struct LNode);
    • 同时定义*LinkList,它是 "指向LNode的指针" 的别名(以后用LinkList可以直接表示链表的头指针)

②不带头结点的单链表

③带头结点的单链表

④小结

六、单链表的插入和删除

①按位序插入(带头结点)

②按位序插入(不带头结点)

③指定结点的后插操作

④指定结点的前插操作

⑤按位序删除(带头结点)

cpp 复制代码
//不带头结点
// 链表结点结构(和带头结点一致)
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;


// 不带表头结点的按位序删除函数
bool ListDelete(LinkList &L, int i, ElemType &e){
    if(i < 1)  // 位序i不合法(位序从1开始)
        return false;

    LNode *p, *q;
    // 特殊情况:删除第1个结点(头指针会改变)
    if(i == 1){
        if(L == NULL)  // 链表为空,无结点可删
            return false;
        q = L;          // q指向要删除的第1个结点
        e = q->data;    // 用e返回被删结点的值
        L = L->next;    // 头指针指向原第2个结点(更新链表头)
        free(q);        // 释放被删结点的空间
        return true;
    }

    // 处理i>1的情况:找到第i-1个结点
    p = L;          // p从第1个结点开始遍历
    int j = 1;      // j记录p当前指向的是第j个结点
    // 循环找到第i-1个结点(要删第i个,需操作其前驱)
    while(p != NULL && j < i-1){
        p = p->next;
        j++;
    }

    if(p == NULL)          // 没找到第i-1个结点(i超过链表长度)
        return false;
    if(p->next == NULL)    // 第i-1个结点后无结点(无法删除第i个)
        return false;

    q = p->next;        // q指向要删除的第i个结点
    e = q->data;        // 用e返回被删结点的值
    p->next = q->next;  // 将*q从链中"断开"
    free(q);            // 释放被删结点的空间
    return true;
}

⑥指定结点的删除

⑦小结

七、单链表的查找

①按位查找

②按值查找

③求表的长度

cpp 复制代码
//不带头结点
// 链表结点结构(和带头结点一致)
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;


// 不带头结点的求表长度函数
int Length(LinkList L){
    int len = 0;    // 统计数据结点的个数
    LNode *p = L;   // p从第一个数据结点开始遍历(L直接指向首个数据结点)
    while (p != NULL){  // 只要当前结点存在(是数据结点),就计数
        len++;
        p = p->next;    // 移动到下一个结点
    }
    return len;
}

④小结

八、单链表的建立

①尾插法

cpp 复制代码
//不带头结点的单链表尾插法代码
LinkList List_TailInsert_NoHead(LinkList &L){ // 不带头结点正向建立单链表
    int x;
    L = NULL; // 初始为空表(不带头结点,L直接指向首节点)
    LNode *s, *r = NULL; // r为表尾指针,初始无节点
    scanf("%d", &x); // 输入节点的值
    while(x != 9999){ // 输入9999表示结束
        s = (LNode *)malloc(sizeof(LNode)); // 分配新节点空间
        s->data = x; // 新节点存数据x
        
        if(L == NULL){ // 处理第一个节点:L指向首个节点
            L = s;
        } else { // 非第一个节点:接在当前表尾r之后
            r->next = s;
        }
        r = s; // 保持r指向最新的表尾节点
        
        scanf("%d", &x); // 继续输入下一个值
    }
    if(r != NULL){ // 若链表非空,尾节点指针置空
        r->next = NULL;
    }
    return L;
}

②头插法

cpp 复制代码
//不带头结点的单链表头插法代码
LinkList List_HeadInsert_NoHead(LinkList &L){ // 不带头结点逆向建立单链表
    int x;
    L = NULL; // 初始为空表(不带头结点,L直接指向首节点)
    LNode *s;
    scanf("%d", &x); // 输入节点的值
    while(x != 9999){ // 输入9999表示结束
        s = (LNode *)malloc(sizeof(LNode)); // 创建新节点
        s->data = x; // 新节点存数据x
        
        s->next = L; // 新节点next指向"当前链表的头"(首次插入时L为NULL)
        L = s; // L更新为"新的链表头"
        
        scanf("%d", &x); // 继续输入下一个值
    }
    return L;
}

③小结

cpp 复制代码
//带头结点的单链表逆置
LinkList ReverseList_WithHead(LinkList L){ // 带头结点的链表逆置
    LNode *p = L->next; // p指向原链表的第一个节点
    L->next = NULL; // 原头结点置空(作为新链表的头)
    LNode *s; // 临时指针存当前节点
    
    while(p != NULL){ // 遍历原链表所有节点
        s = p; // 取出当前节点
        p = p->next; // p提前后移(避免后续操作断链)
        
        // 头插:将s插入新链表的头结点之后
        s->next = L->next;
        L->next = s;
    }
    return L; // 返回逆置后的链表头
}

//不带头结点的单链表逆置
LinkList ReverseList_NoHead(LinkList L){ // 不带头结点的链表逆置
    LNode *p = L; // p指向原链表的第一个节点
    LinkList newL = NULL; // 新链表初始为空
    LNode *s; // 临时指针存当前节点
    
    while(p != NULL){ // 遍历原链表所有节点
        s = p; // 取出当前节点
        p = p->next; // p提前后移
        
        // 头插:将s插入新链表的头部
        s->next = newL;
        newL = s; // newL更新为新的链表头
    }
    return newL; // 返回逆置后的链表头
}

九、双链表

①初始化

②插入

③删除

④遍历

⑤小结

十、循环链表

①循环单链表

②循环双链表初始化

③循环双链表插入

④循环双链表删除

⑤小结

十一、静态链表

①什么是静态链表

②用代码定义一个静态链表

③基本操作

④小结

十二、顺序表和链表的比较

相关推荐
SmartRadio2 小时前
ESP32添加修改蓝牙名称和获取蓝牙连接状态的AT命令-完整UART BLE服务功能后的完整`main.c`代码
c语言·开发语言·c++·esp32·ble
余瑜鱼鱼鱼2 小时前
Java数据结构:从入门到精通(十二)
数据结构
实心儿儿2 小时前
Linux —— 基础开发工具5
linux·运维·算法
charlie1145141913 小时前
嵌入式的现代C++教程——constexpr与设计技巧
开发语言·c++·笔记·单片机·学习·算法·嵌入式
济6174 小时前
嵌入式C语言(第二期)
c语言
清木铎5 小时前
leetcode_day4_筑基期_《绝境求生》
算法
清木铎5 小时前
leetcode_day10_筑基期_《绝境求生》
算法
j_jiajia5 小时前
(一)人工智能算法之监督学习——KNN
人工智能·学习·算法
源代码•宸5 小时前
Golang语法进阶(协程池、反射)
开发语言·经验分享·后端·算法·golang·反射·协程池