数据结构--单链表

数据结构基础(3)

文章目录

  • 数据结构基础(3)
      • 单链表的定义:
      • 不带头结点的单链表:
      • 带头结点的单链表:
      • 单链表的插入操作:
        • 按位序插入(带头结点):
        • 按位序插入(不带头结点):
      • 指定结点的后插操作:
      • 指定结点的前插操作:
      • 按位序删除(带头结点):
      • 按位查找:
      • 按值查找:
      • 求表的长度:
      • 单链表的建立--尾插法
      • 单链表的建立--头插法

单链表的定义:

带头结点

不带头结点

顺序表:

优点:可随机存取,存储密度高

缺点:要求大片连续空间,改变容量不方便

单链表:

优点:不要求大片连续空间,改变容量方便

缺点:不可随机存储,要耗费一定空间放指针

定义:

struct LNode{
    ElemType data;       // 每个节点存放一个数据元素
    struct LNode *next;  // 指针指向下一个节点
};

//LNode 结点
//data 数据域
//next 指针域

struct LNode * p = (struct LNode *) malloc (sizeof (struct LNode))

增加一个新的结点:在内存中申请一个结点所需空间,并用指针p指向这个结点

typedef <数据类型><别名>

typedef struct LNode LNode;

LNode *p = ( LNode *)malloc (sizeof(LNode));

// 定义单链表结点类型
typedef struct LNode {  
    ElemType data;  // 每个节点存放一个数据元素
    struct LNode *next;  // 指针指向下一个节点
} LNode, *LinkList;  

// 获取单链表中指定位置的结点
LNode *GetElem(LinkList L, int i) {  
    int j = 1;  
    LNode *p = L->next;  
    if (i == 0)  
        return L;  
    if (i < 1)  
        return NULL;  
    while (p != NULL && j < i) {  
        p = p->next;  
        j++;  
    }  
    return p;  
}  

强调这是一个单链表 – 使用LinkList

强调这是一个结点 – 使用LNode *

不带头结点的单链表:

// 定义单链表结点类型
typedef struct LNode {  
    ElemType data;  // 每个节点存放一个数据元素
    struct LNode *next;  // 指针指向下一个节点
} LNode, *LinkList;  

// 初始化一个空的单链表(不带头结点)
bool InitList(LinkList &L) {  
    L = NULL;  // 空表,暂时还没有任何结点,防止脏数据
    return true;  
}  

// 判断单链表是否为空
bool Empty(LinkList L) {  
    // 链表头指针为 NULL 则为空
    return (L == NULL);  
}  

void test() {  
    LinkList L;  // 声明一个指向单链表的指针
    InitList(L);    // 初始化一个空表
    //......后续代码......
}  

带头结点的单链表:

// 定义单链表结点类型
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 = NULL;  // 头结点之后暂时还没有节点
    return true;  
}  

// 判断单链表是否为空(带头结点)
bool Empty(LinkList L) {  
    // 头结点的 next 指针为 NULL 则链表为空
    return (L->next == NULL);  
}  

// 测试函数,用于演示带头结点链表的初始化和判空等操作
void test() {  
    LinkList L;  // 声明一个指向单链表的指针
    // 初始化一个空表(带头结点)
    InitList(L);  
    //......后续代码......
}  

单链表的插入操作:

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

ListInsert(&L,i,e) : 插入操作:在表L中的第i个位置上插入指定元素e

找到第i-1个结点,将新结点插入其后,头结点可以看作是“第0个”结点

// 定义单链表结点类型
typedef struct LNode {  
    ElemType data;  // 每个节点存放一个数据元素
    struct LNode *next;  // 指针指向下一个节点
} LNode, *LinkList;  

// 在带头结点的单链表第 i 个位置插入元素 e
bool ListInsert(LinkList &L, int i, ElemType e) {  
    if (i < 1)   // 插入位置不合法(小于 1 )
        return false;  
    LNode *p;  // 指针 p 指向当前扫描到的结点
    int j = 0;  // 当前 p 指向的是第几个结点
    p = L;  // L 指向头结点,头结点是第 0 个结点(不存数据)
    while (p != NULL && j < i - 1) {   // 循环找到第 i - 1 个结点
        p = p->next;  
        j++;  
    }  
    if (p == NULL)  // i 值不合法(p 为 NULL ,说明没找到对应位置 )
        return false;  
    LNode *s = (LNode *)malloc(sizeof(LNode));   // 分配新结点
    s->data = e;  
    s->next = p->next;  
    p->next = s;  // 将结点 s 连到 p 之后
    return true;  // 插入成功
}  
按位序插入(不带头结点):

ListInsert(&L,i,e) : 插入操作:在表L中的第i个位置上插入指定元素e

找到第i-1个结点,将新结点插入其后,不存在“第0个”结点,因此 i = 1 时需要特殊处理

// 定义单链表结点类型
typedef struct LNode {  
    ElemType data;  // 每个节点存放一个数据元素
    struct LNode *next;  // 指针指向下一个节点
} LNode, *LinkList;  

// 在不带头结点的单链表第 i 个位置插入元素 e
bool ListInsert(LinkList &L, int i, ElemType e) {  
    // 插入位置不合法(小于 1 )
    if (i < 1)  
        return false;  
    // 插入第 1 个结点的操作与其他结点操作不同
    if (i == 1) {  
        LNode *s = (LNode *)malloc(sizeof(LNode));  
        s->data = e;  
        s->next = L;  
        L = s;  // 头指针指向新结点
        return true;  
    }  
    LNode *p;  // 指针 p 指向当前扫描到的结点
    int j = 1;  // 当前 p 指向的是第几个结点
    p = L;  // p 指向第 1 个结点(注意:不是头结点 )
    // 循环找到第 i - 1 个结点
    while (p != NULL && j < i - 1) {  
        p = p->next;  
        j++;  
    }  
    if (p == NULL)  // i 值不合法(p 为 NULL ,说明没找到对应位置 )
        return false;  
    LNode *s = (LNode *)malloc(sizeof(LNode));   // 分配新结点
    s->data = e;  
    s->next = p->next;  
    p->next = s;  // 插入结点
    return true;  // 插入成功
}  

如果不带头结点,则插入、删除第1个元素时,需要更改指针L

指定结点的后插操作:

// 定义单链表结点类型
typedef struct LNode {  
    ElemType data;  // 每个节点存放一个数据元素
    struct LNode *next;  // 指针指向下一个节点
} LNode, *LinkList;  

// 后插操作:在 p 结点之后插入元素 e
bool InsertNextNode(LNode *p, ElemType e) {  
    if (p == NULL)  
        return false;  
    LNode *s = (LNode *)malloc(sizeof(LNode));  
    if (s == NULL)  // 内存分配失败
        return false;  
    s->data = e;  // 用结点 s 保存数据元素 e
    s->next = p->next;  
    p->next = s;  // 将结点 s 连到 p 之后
    return true;  
}  

// 在带头结点的单链表第 i 个位置插入元素 e
bool ListInsert(LinkList &L, int i, ElemType e) {  
    // 插入位置不合法(小于 1 )
    if (i < 1)  
        return false;  
    LNode *p;  // 指针 p 指向当前扫描到的结点
    int j = 0;  // 当前 p 指向的是第几个结点
    p = L;  // L 指向头结点,头结点是第 0 个结点(不存数据)
    // 循环找到第 i - 1 个结点
    while (p != NULL && j < i - 1) {  
        p = p->next;  
        j++;  
    }  
    // i 值不合法(p 为 NULL ,说明没找到对应位置 )
    if (p == NULL)  
        return false;  
    // 调用后插操作函数完成插入
    return InsertNextNode(p, e);  
}  

指定结点的前插操作:

// 定义单链表结点类型
typedef struct LNode {  
    ElemType data;  // 每个节点存放一个数据元素
    struct LNode *next;  // 指针指向下一个节点
} LNode;  

// 前插操作:在 p 结点之前插入元素 e
bool InsertPriorNode(LNode *p, ElemType e) {  
    // p 为 NULL ,无法插入
    if (p == NULL)  
        return false;  
    LNode *s = (LNode *)malloc(sizeof(LNode));  // 分配新结点
    // 内存分配失败
    if (s == NULL)  
        return false;  
    s->next = p->next;  // 新结点 s 连到 p 之后
    p->next = s;        // 新结点 s 连到 p 之后
    s->data = p->data;  // 将 p 中元素复制到 s 中
    p->data = e;        // p 中元素覆盖为 e
    return true;  
}  

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

ListDelete(&L,&e) : 删除操作:删除表L中第i个位置的元素,并用e返回删除元素的值

// 定义单链表结点类型
typedef struct LNode {  
    ElemType data;  // 每个节点存放一个数据元素
    struct LNode *next;  // 指针指向下一个节点
} LNode, *LinkList;  

// 在位序 i 处删除带头结点单链表中的结点(并用 e 返回被删元素值 )
bool ListDelete(LinkList &L, int i, ElemType &e) {  
    // 删除位置不合法(小于 1 )
    if (i < 1)  
        return false;  
    LNode *p;  // 指针 p 指向当前扫描到的结点
    int j = 0;  // 当前 p 指向的是第几个结点
    p = L;  // L 指向头结点,头结点是第 0 个结点(不存数据)
    // 循环找到第 i - 1 个结点
    while (p != NULL && j < i - 1) {  
        p = p->next;  
        j++;  
    }  
    if (p == NULL)  // i 值不合法(p 为 NULL ,说明没找到对应位置 )
        return false;  
    // 第 i - 1 个结点之后已无其他结点,无法删除
    if (p->next == NULL)  
        return false;  
    LNode *q = p->next;  // 令 q 指向被删除结点
    e = q->data;  // 用 e 返回元素的值
    p->next = q->next;  // 将 *q 结点从链中“断开”
    free(q);  // 释放结点的存储空间
    return true;   // 删除成功
}  

单链表的局限性:无法逆向检索,有时候不太方便

按位查找:

// 定义单链表结点类型
typedef struct LNode {  
    ElemType data;  // 每个节点存放一个数据元素
    struct LNode *next;  // 指针指向下一个节点
} LNode, *LinkList;  

// 按位查找,返回带头结点单链表中第 i 个元素对应的结点
LNode *GetElem(LinkList L, int i) {  
    // 查找位置不合法(小于 0 )
    if (i < 0)  
        return NULL;  
    LNode *p;  // 指针 p 指向当前扫描到的结点
    int j = 0;  // 当前 p 指向的是第几个结点
    p = L;  // L 指向头结点,头结点是第 0 个结点(不存数据)
    // 循环找到第 i 个结点
    while (p != NULL && j < i) {  
        p = p->next;  
        j++;  
    }  
    return p;  
}  

平均时间复杂度:O(n)

按值查找:

假设 ElemType是int 类型

// 定义单链表结点类型
typedef struct LNode {  
    ElemType data;  // 每个节点存放一个数据元素
    struct LNode *next;  // 指针指向下一个节点
} LNode, *LinkList;  

// 按值查找,找到单链表中数据域等于 e 的结点并返回其指针
LNode *LocateElem(LinkList L, ElemType e) {  
    // 从第 1 个结点开始查找
    LNode *p = L->next;  
    // 遍历链表,找数据域为 e 的结点
    while (p != NULL && p->data != e) {  
        p = p->next;  
    }  
    // 找到后返回该结点指针,否则返回 NULL
    return p;  
}  

平均时间复杂度:O(n)

求表的长度:

// 假设已定义单链表结点结构及类型
typedef struct LNode {  
    // 数据域,需根据实际补充类型,比如 int data; 
    // 这里按原代码逻辑,用通用结构
    struct LNode *next;  
} LNode, *LinkList;  

// 求带头结点单链表的长度
int Length(LinkList L) {  
    int len = 0;  // 统计表长
    LNode *p = L;  
    while (p->next != NULL) {  
        p = p->next;  
        len++;  
    }  
    return len;  
}  

单链表的建立–尾插法

s1:初始化一个单链表

s2:每次取一个数据元素,插入到表尾/表头

// 定义单链表结点类型
typedef struct LNode {  
    // 这里按代码中假设 ElemType 为整型,直接用 int
    int data;  
    struct LNode *next;  
} LNode, *LinkList;  

// 尾插法正向建立单链表
LinkList List_TailInsert(LinkList &L) {  
    int x;  // 设 ElemType 为整型,存储结点值
    // 建立头结点
    L = (LinkList)malloc(sizeof(LNode));  
    LNode *s, *r = L;  // r 为表尾指针
    scanf("%d", &x);  // 输入结点的值
    // 输入 9999 表示结束
    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;  
}  

时间复杂度:O(n)

单链表的建立–头插法

// 定义单链表结点类型
typedef struct LNode {  
    // 数据域,代码中用 int 类型存储数据
    int data;  
    struct LNode *next;  
} LNode, *LinkList;  

// 头插法逆向建立单链表
LinkList List_HeadInsert(LinkList &L) {  
    LNode *s;  
    int x;  
    // 创建头结点
    L = (LinkList)malloc(sizeof(LNode));  
    L->next = NULL;  // 初始为空链表
    scanf("%d", &x);  // 输入结点的值
    // 输入 9999 表示结束
    while (x != 9999) {  
        // 创建新结点
        s = (LNode *)malloc(sizeof(LNode));  
        s->data = x;  
        s->next = L->next;  
        L->next = s;  // 将新结点插入表中,L 为头指针
        scanf("%d", &x);  
    }  
    return L;  
}  

应用:链表的逆置

头插法、尾插法,核心就是初始化操作、指定结点的后插操作

你可能感兴趣的:(数据结构--单链表)