数据结构(二) 线性表

发布于:2025-05-20 ⋅ 阅读:(20) ⋅ 点赞:(0)

一. 线性表

        1.定义

                线性表是由n(n>=0)个具有相同数据类型的数据元素构成的有限序列。其中,元素之间通过顺序关系排列,每个元素有且只有一个直接前驱和一个直接后继(除首尾元素外)

二.线性表的顺序表示(顺序表)

        1.存储方式

                使用连续的内存空间(数组)存储元素,逻辑相邻的元素在物理位置上也相邻

        2.特点

                优点:支持随机访问,时间复杂度为O(1)

                缺点:进行插入/删除操作时,需要移动元素,平均时间复杂度为O(n)

        3.动态扩容

                当数组空间不足时,可以重新分配更大的空间,并复制数据

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define MAXSIZE 100  // 顺序表初始最大容量

// 顺序表结构体定义
typedef struct SeqList{
    int *data;       // 指向动态分配数组的指针
    int length;      // 当前表长(有效元素个数)
    int capacity;    // 当前分配的存储容量
}SeqList;

/* 初始化顺序表
 * 参数:L - 指向顺序表结构的指针
 * 流程:
 * 1. 申请MAXSIZE大小的连续内存空间
 * 2. 初始化长度为0
 * 3. 设置当前容量为MAXSIZE
 */
void InitList(SeqList *L){
    L->data = (int *)malloc(sizeof(int) * MAXSIZE);  // 分配初始内存
    L->length = 0;               // 初始空表
    L->capacity = MAXSIZE;       // 记录最大容量
}

/* 插入元素函数
 * 参数:L - 顺序表指针,index - 插入位置(从1开始),value - 插入值
 * 返回值:bool - 是否插入成功
 * 流程:
 * 1. 位置有效性检查(1<=index<=length+1)
 * 2. 空间不足时自动扩容(2倍扩容策略)
 * 3. 元素后移操作(从最后一个元素开始到目标位置)
 * 4. 插入新元素并更新表长
 */
bool InsertList(SeqList *L, int index, int value){
   // 位置合法性校验(包括空表情况下的校验)
   if(index < 1 || index > L->length + 1){
        return false;  // 插入位置非法
   }
   
   // 空间扩容机制(当表满时自动扩容)
   if(L->length >= L->capacity){
        // 尝试扩容为当前容量的2倍
        int *NewData = (int *)realloc(L->data, sizeof(int) * L->capacity * 2);
        if(!NewData){  // 内存分配失败
            return false;
        } 
        L->data = NewData;     // 更新数据指针
        L->capacity *= 2;      // 更新容量记录
    }
    
    // 元素后移操作(从后往前移动防止覆盖)
    for(int i = L->length; i >= index; i--){
        L->data[i] = L->data[i-1];  // 将index-1位置后的元素依次后移
    }
    
    // 插入新元素并更新表长
    L->data[index-1] = value;  // 数组下标从0开始,故减1
    L->length++;               // 表长增加
    return true;
}

/* 打印顺序表
 * 参数:L - 顺序表指针
 * 流程:
 * 1. 遍历有效元素
 * 2. 空格分隔元素
 * 3. 末尾换行
 */
void PrintList(SeqList *L){
    for(int i = 0; i < L->length; i++){
        printf("%d ", L->data[i]);  // 逐个输出元素
    }
    printf("\n");  // 换行保持输出格式
}

/* 主函数测试流程
 * 测试策略:
 * 1. 初始化测试
 * 2. 正常插入(头部、中间、尾部)
 * 3. 边界测试(尾部追加)
 * 4. 异常测试(非法位置插入)
 */
int main() {
    SeqList L;
    InitList(&L);
    
    // 初始状态验证
    printf("初始顺序表:");
    PrintList(&L);  // 应输出 []
    
    // 正常插入测试(验证不同位置的插入逻辑)
    InsertList(&L, 1, 10);  // 首部插入(空表插入)
    InsertList(&L, 2, 30);  // 尾部插入(容量足够时)
    InsertList(&L, 2, 20);  // 中间插入(触发元素移动)
    printf("插入3个元素后:");
    PrintList(&L);  // 应输出 [10 20 30]
    
    // 边界测试(验证最大容量的插入)
    InsertList(&L, 4, 40);  // 尾部追加(验证边界条件)
    printf("尾部追加后:");
    PrintList(&L);  // 应输出 [10 20 30 40]
    
    // 异常测试(验证错误处理能力)
    bool result = InsertList(&L, 6, 50);  // 非法位置(当前长度4,只能插入到位置5)
    printf("非法插入结果:%s\n", result ? "成功" : "失败");  // 应输出失败
    
    return 0;
}

 三.线性表的链式表示和实现(链表)

        1.存储方式

               通过节点(包含数据域和指针域)动态分配内存,逻辑相邻的元素在物理位置上不一定相邻

        2.类型

                单链表:     每个节点指向下一个结点

                双链表:     节点包含前驱和后继指针

                循环链表:  尾节点指向头节点

        3.特点

                优点: 进行插入/删除操作时高效,时间复杂度为O(1)

                缺点: 无法随机访问节点值,必须从头遍历,时间复杂度为O(n)  

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

/* 链表节点结构体
 * Data: 节点存储的数据(整型)
 * Next: 指向下一个节点的指针 */
typedef struct Node{
    int Data;
    struct Node *Next;
}Node;

/* 初始化链表头节点
 * 返回值:已分配内存的头节点指针
 * 注意:头节点不存储有效数据,仅作为链表入口 */
Node* InitList(){
    Node* Head = (Node*)malloc(sizeof(Node));
    Head->Next = NULL;
    return Head;
}

/* 头插法插入节点
 * Head: 链表头节点指针
 * Value: 要插入的整型数据
 * 流程:1.创建新节点 2.调整指针指向 */
void InsertHead(Node* Head, int Value){
    Node* NewNode = (Node*)malloc(sizeof(Node));
    NewNode->Data = Value;
    NewNode->Next = Head->Next;
    Head->Next = NewNode;
}

/* 尾插法插入节点
 * Head: 链表头节点指针
 * Value: 要插入的整型数据
 * 流程:1.遍历到最后一个节点 2.在末尾插入新节点 */
void InsertTail(Node* Head, int Value){
    Node* Current = Head;
    while(Current->Next != NULL){
        Current = Current->Next;
    }
    Node* NewNode = (Node*)malloc(sizeof(Node));
    NewNode->Data = Value;
    NewNode->Next = NULL;
    Current->Next = NewNode;
}

/* 删除指定值的节点
 * Head: 链表头节点指针
 * Value: 要删除的整型数据
 * 返回值:true删除成功,false未找到数据
 * 流程:双指针法遍历,找到后调整指针并释放内存 */
bool DeleteNode(Node* Head, int Value){
   Node* Previous = Head;
   Node* Current = Head->Next;
   while(Current!=NULL){
    if(Current->Data == Value){
        Previous->Next = Current->Next;
        free(Current);
        return true;
    }
    Previous = Current;
    Current = Current->Next;
   }
   return false;
}

/* 修改指定位置节点的值
 * Head: 链表头节点指针
 * pos: 要修改的位置(从0开始计数)
 * NewValue: 新的整型数据值
 * 注意:如果位置超出链表长度,不会修改任何节点 */
void ModifyNode(Node* Head, int pos, int NewValue){
    Node* Current = Head->Next;
    int count = 0;
    while(Current!=NULL && count<pos){
        Current = Current->Next;
        count++;
    }
    if(Current!=NULL){
        Current->Data = NewValue;
    }
}

/* 搜索节点位置
 * Head: 链表头节点指针
 * Value: 要查找的整型数据
 * 返回值:找到返回位置索引(从0开始),未找到返回-1 */
int SearchNode(Node* Head, int Value){
    Node* Current = Head->Next;
    int pos = 0;
    while(Current!=NULL){
        if(Current->Data == Value){
            return pos;
        }
        Current = Current->Next;
        pos++;
    }
    return -1;
}

/* 打印整个链表
 * Head: 链表头节点指针
 * 输出格式:数据->数据->...->NULL */
void PrintList(Node* Head){
    Node* Current = Head->Next;
    while(Current!=NULL){
        printf("%d->", Current->Data);
        Current = Current->Next;
    }
    printf("NULL \n"); 
}

/* 反转链表
 * Head: 链表头节点指针
 * 实现方式:三指针法原地反转
 * 流程:1.逐个节点反转指向 2.最后更新头节点指针 */
void ReverseList(Node* Head){
    Node* Previous = NULL;
    Node* Current = Head->Next;
    Node* Next = NULL; 
    while(Current!=NULL){
        Next = Current->Next;
        Current->Next = Previous;
        Previous = Current;
        Current = Next; 
    }
    Head->Next = Previous;
}

/* 清空链表(保留头节点)
 * Head: 链表头节点指针
 * 流程:逐个释放所有节点内存,最后重置头节点指针 */
void ClearList(Node* Head){
    Node* Current = Head->Next;
    Node* Next = NULL;
    while(Current!=NULL){
        Next = Current->Next;
        free(Current);
        Current = Next;
    }
    Head->Next = NULL; 
}
int main(){
    Node* Head = InitList();
    InsertHead(Head, 1);
    InsertHead(Head, 2);
    InsertHead(Head, 3);
    InsertTail(Head, 4);
    InsertTail(Head, 5);
    PrintList(Head);
    DeleteNode(Head, 3);
    PrintList(Head);
    ModifyNode(Head, 1, 6);
    PrintList(Head);
    printf("Position of 6: %d\n", SearchNode(Head, 6));
    ReverseList(Head);
    PrintList(Head);
    ClearList(Head);
    PrintList(Head);
    return 0; 
}
        4.部分函数解析

                (1)链表逆置(ReverseList):

/* 三指针法实现链表逆置
 * 时间复杂度:O(n) 空间复杂度:O(1) 
 * 执行过程:
 * 1. 初始化三个指针:Previous(前驱)、Current(当前)、Next(后继)
 * 2. 遍历链表,逐个反转节点指向
 * 3. 最后更新头节点指针 */
void ReverseList(Node* Head){
    Node* Previous = NULL;       // 前驱指针初始化为空
    Node* Current = Head->Next; // 当前指针指向第一个有效节点
    Node* Next = NULL;           // 后继指针用于临时存储
    
    while(Current != NULL){
        Next = Current->Next;    // 保存下一个节点
        Current->Next = Previous;// 反转指针方向
        Previous = Current;      // 前驱指针前移
        Current = Next;          // 当前指针前移
    }
    Head->Next = Previous;       // 更新头节点指向新的首节点
}

                (2) 头插法(InsertHead)

/* 头插法特点:
 * 1. 新节点总是插入在链表头部
 * 2. 插入顺序与最终链表顺序相反
 * 3. 时间复杂度:O(1) */
void InsertHead(Node* Head, int Value){
    Node* NewNode = (Node*)malloc(sizeof(Node));
    NewNode->Data = Value;
    NewNode->Next = Head->Next;  // 新节点指向原首节点
    Head->Next = NewNode;        // 头节点指向新节点
}

                 (3)尾插法(InsertTail)

/* 尾插法特点:
 * 1. 新节点总是追加在链表末尾
 * 2. 插入顺序与最终链表顺序一致
 * 3. 时间复杂度:O(n) */
void InsertTail(Node* Head, int Value){
    Node* Current = Head;
    while(Current->Next != NULL){ // 遍历到最后一个节点
        Current = Current->Next;
    }
    Node* NewNode = (Node*)malloc(sizeof(Node));
    NewNode->Data = Value;
    NewNode->Next = NULL;         // 新节点作为末节点
    Current->Next = NewNode;      // 原末节点指向新节点
}


网站公告

今日签到

点亮在社区的每一天
去签到