2.1 链表1

文章目录

一、不带头结点的单链表基本操作

(1)单链表结构体

🌽代码

【写法1】这里使用这个写法

cpp 复制代码
typedef int ElemType;
typedef struct LNode{
    ElemType data;	//数据域
    LNode* next;	//指针域,保存后继结点的地址
}LNode,*LinkedLIst;

注意:

1、LNode* p(强调节点) 与 LinkedList p(强调链表)无区别。

2、头指针:每次指向链表的第一个节点。

头结点:申请的头结点是虚拟的节点,里面不存放数据信息,真正有意义的在头结点后面,头结点只是为了方便操作。指向第一个元素的依然叫做"头指针"。

【写法2】

cpp 复制代码
typedef struct LNode{
    ElemType data;
    LNode* next;
}LNode;

typedef struct {
    LNode* head;
    int length;	//用来表示表长
}LinkedList;

(2)单链表初始化

🌽代码

cpp 复制代码
//初始化
void initLinkedList(LinkedList& L) {
    L = NULL;	//直接为空即可
}

(3)头插法创建单链表

🌽代码

【写法1】

cpp 复制代码
//头插法创建单链表
void createListByHead(LinkedList& L) {
    ElemType x;
    scanf("%d", &x);
    while (x != 999) {
        LNode* node = (LNode*)malloc(sizeof(LNode));	//申请节点空间
        if (node == NULL) {	//若是申请空间失败,则退出
            return;
        }
        node->data = x;
        node->next = NULL;
        
        if (L == NULL) {	//是第一个节点
            L=node;
        }
        else {	//不是第一个节点
            node->next=L;
            L = node;
        }
        scanf("%d", &x);	//继续输入
    }
}

【写法2】优化版,这里使用这一种写法

cpp 复制代码
void createListByHead(LinkedList& L) {
    ElemType x;
    scanf("%d", &x);
    while (x != 999) {
        LNode* node = (LNode*)malloc(sizeof(LNode));	//申请节点空间
        node->data = x;
        node->next = NULL;

        if(L!=NULL){	
            node->next = L;
        }
        L = node;
        scanf("%d", &x);	//继续输入
    }
}

头插法读入数据的顺序,与链表中的元素顺序相反

🍺测试结果

(4)打印单链表

🌽代码

cpp 复制代码
//打印单链表
void printLinkedList(LinkedList L) {
    LNode* p = L;	//p指向头指针处
    while (p!= NULL) {	//p不为空才打印
        printf("%d->", p->data);
        p = p->next;	//p后移
    }
    printf("\n");
}

(5)尾插法创建单链表

🌽代码

cpp 复制代码
//尾插法创建单链表
void createListByTail(LinkedList& L) {
    ElemType x;
    scanf("%d", &x);

    LNode* p = NULL;	//p用来指向链表的最后一个节点

    while (x != 999) {
        LNode* node = (LNode*)malloc(sizeof(LNode));
        node->data = x;
        node->next = NULL;

        if (L == NULL) {	//创建的是第一个节点
            L = node;	//node为创建的第一个节点
            p = node;
        }
        else {	//不是第一个节点
            p->next = node;	//先动node再动p
            p = node;
        }
        scanf("%d", &x);
    }
}

🍺测试结果

有一个表尾指针p,始终指向最后一个节点。注意每次先将node放好,然后再动p。

尾插法:输出结果与输入结果顺序一致

(6)按位查找

🌽代码

cpp 复制代码
//返回第pos位置上的节点
LNode* getNode(LinkedList L, int pos) {
    if (pos < 1) {	//pos不合法
        return NULL;
    }
    int i = 1;	//i用于计数,从1开始
    LNode* p = L;	//p用于遍历单链表
    while (i < pos && p!=NULL) {	//注意p不能为空,为空的时候就是表尾了
        p = p->next;
        i++;
    }
    return p;
}

🍺测试结果

(7)按值查找

🌽代码

cpp 复制代码
//按值查找节点
LNode* locateElem(LinkedList L, ElemType e) {
    LNode* p = L;
    while (p != NULL &&p->data!=e) {	//p!=NULL可以简写为p
        p = p->next;
    }
    return p;
}

(8)求表长

🌽代码

cpp 复制代码
//求表长(从头到尾遍历即可)
int length(LinkedList L) {
    LNode* p = L;
    int i = 0;	//注意i从0开始,p不是空的时候才加一
    while (p != NULL) {
        p = p->next;
        i++;
    }
    return i;
}

🍺测试结果

(9)单链表的插入

🌽代码

【写法1】

cpp 复制代码
//在第pos位置插入元素e
void insertElem(LinkedList& L, int pos, ElemType e) {
    //pos合法范围为:[1,length+1],可以在最后位置后面插入
    if (pos<1 || pos>length(L)+1) {	//pos不合法,退出
        return;
    }
    LNode* p = L;	//p为遍历指针
    int i = 1;	//i计数
    LNode* node = (LNode*)malloc(sizeof(LNode));
    node->data = e;
    node->next = NULL;

    //在第一个位置上插入node
    if (pos == 1) {	
        node->next = L;
        L = node;
    }
    else {	//在其余位置包括最后的位置插入node
        while (i < pos - 1) {	//找第pos位置的前一个节点
            p = p->next;
            i++;
        }
        node->next = p->next;
        p->next = node;
    }
}

【写法2】

cpp 复制代码
//在第pos位置插入元素e
void insertElem(LinkedList& L, int pos, ElemType e) {
    //pos合法范围为:[1,length+1],可以在最后位置后面插入
    if (pos<1 || pos>length(L)+1) {	//pos不合法,退出
        return;
    }

    int i = 1;	//i计数
    LNode* node = (LNode*)malloc(sizeof(LNode));	//开辟新节点
    node->data = e;
    node->next = NULL;

    if (pos == 1) {	//在第一个位置上插入node
        node->next = L;
        L = node;
    }
    else {	//在其余位置包括最后的位置插入node
        //直接调用前面写的函数
        LNode* pre = getNode(L, pos - 1);

        node->next = pre->next;
        pre->next = node;
    }
}

🍺测试结果

又如:

又如:

(10)单链表的删除

🌽代码

【写法1】

cpp 复制代码
void DeleteElem(LinkedList& L, int pos, ElemType& e) {
    //pos合法范围为:[1,length]
    if (pos<1 || pos>length(L)) {
        return;
    }
    LNode* p = getNode(L, pos);	//找到pos位置的节点,要删除的就是它
    if (pos == 1) {	//删除第一个位置的节点
        L = p->next;
    }
    else {	//删除其他位置的节点
        LNode* pr=getNode(L, pos-1);	//找到pos-1位置的节点
        pr->next = p->next;
    }
    e = p->data;
    free(p);	//释放空间
}

【写法2】不推荐,可以选择法一直接使用get函数

cpp 复制代码
//删除第pos位置的节点,并且返回被删除节点的元素值e
void DeleteElem(LinkedList& L, int pos, ElemType& e) {
    //pos合法范围为:[1,length]
    if (pos<1 || pos>length(L)) {
        return;
    }

    int i = 1;
    int j = 1;
    LNode* p = L;	//p为遍历指针
    LNode* pr = L;	//pr为遍历指针
    if (pos == 1) {
        e = L->data;
        L = L->next;
    }
    else {
        while (i < pos) {	//找到第pos位置的节点
            i++;
            p = p->next;
        }
        while (j < pos - 1) {	//找到第pos-1位置的节点
            j++;
            pr = pr->next;
        }
        e = p->data;
        pr->next=p->next;
    }
}

🍺测试结果

又如:

(11)整体代码

LinkedList.h

cpp 复制代码
#pragma once
typedef int ElemType;
typedef struct LNode{
    ElemType data;	//数据域
    LNode* next;	//指针域,保存后继结点的地址
}LNode,*LinkedList;

//注意:LNode* p(强调节点) 与 LinkedList p(强调链表)无区别
//typedef struct LNode{
//	ElemType data;
//	LNode* next;
//}LNode;
//
//typedef struct {
//	LNode* head;
//	int length;	//用来表示表长
//}LinkedList;

//初始化
void initLinkedList(LinkedList& L);

//头插法创建单链表
void createListByHead(LinkedList& L);

//打印单链表
void printLinkedList(LinkedList L);

//尾插法创建单链表
void createListByTail(LinkedList& L);

//返回第pos位置上的节点
LNode* getNode(LinkedList L, int pos);

//按值查找节点
LNode* locateElem(LinkedList L, ElemType e);

//求表长
int length(LinkedList L);

//在第pos位置插入元素e
void insertElem(LinkedList& L, int pos, ElemType e);

//删除第pos位置的节点,并且返回被删除节点的元素值e
void DeleteElem(LinkedList& L, int pos, ElemType& e);

LinkedList.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include "LinkedList.h"

//初始化
void initLinkedList(LinkedList& L) {
    L = NULL;	//直接为空即可
}

//头插法创建单链表
//void createListByHead(LinkedList& L) {
//	ElemType x;
//	scanf("%d", &x);
//	while (x != 999) {
//		LNode* node = (LNode*)malloc(sizeof(LNode));	//申请节点空间
//		if (node == NULL) {	//若是申请空间失败,则退出
//			return;
//		}
//		node->data = x;
//		node->next = NULL;
//		
//		if (L == NULL) {	//是第一个节点
//			L=node;
//		}
//		else {	//不是第一个节点
//			node->next=L;
//			L = node;
//		}
//		scanf("%d", &x);	//继续输入
//	}
//}

void createListByHead(LinkedList& L) {
    ElemType x;
    scanf("%d", &x);
    while (x != 999) {
        LNode* node = (LNode*)malloc(sizeof(LNode));	//申请节点空间
        node->data = x;
        node->next = NULL;

        if(L!=NULL){	
            node->next = L;
        }
        L = node;
        scanf("%d", &x);	//继续输入
    }
}


//打印单链表
void printLinkedList(LinkedList L) {
    LNode* p = L;	//p指向头指针处
    while (p!= NULL) {
        printf("%d->", p->data);
        p = p->next;	//p后移
    }
    printf("\n");
}

//尾插法创建单链表
void createListByTail(LinkedList& L) {
    ElemType x;
    scanf("%d", &x);

    LNode* p = NULL;	//p用来指向链表的最后一个节点

    while (x != 999) {
        LNode* node = (LNode*)malloc(sizeof(LNode));
        node->data = x;
        node->next = NULL;

        if (L == NULL) {	//创建的是第一个节点
            L = node;	//node为创建的第一个节点
            p = node;
        }
        else {	//不是第一个节点
            p->next = node;	//先动node再动p
            p = node;
        }
        scanf("%d", &x);
    }
}

//返回第pos位置上的节点
LNode* getNode(LinkedList L, int pos) {
    if (pos < 1) {	//pos不合法
        return NULL;
    }
    int i = 1;	//i用于计数
    LNode* p = L;	//p用于遍历单链表
    while (i < pos && p!=NULL) {	//注意p不能为空,为空的时候就是表尾了
        p = p->next;
        i++;
    }
    return p;
}

//按值查找节点
LNode* locateElem(LinkedList L, ElemType e) {
    LNode* p = L;
    while (p != NULL &&p->data!=e) {	//p!=NULL可以简写为p
        p = p->next;
    }
    return p;
}

//求表长(从头到尾遍历即可)
int length(LinkedList L) {
    LNode* p = L;
    int i = 0;	//注意i从0开始,p不是空的时候才加一
    while (p != NULL) {
        p = p->next;
        i++;
    }
    return i;
}

//在第pos位置插入元素e
void insertElem(LinkedList& L, int pos, ElemType e) {
    //pos合法范围为:[1,length+1],可以在最后位置后面插入
    if (pos<1 || pos>length(L)+1) {	//pos不合法,退出
        return;
    }
    //LNode* p = L;	//p为遍历指针
    int i = 1;	//i计数
    LNode* node = (LNode*)malloc(sizeof(LNode));
    node->data = e;
    node->next = NULL;

    //在第一个位置上插入node
    if (pos == 1) {	
        node->next = L;
        L = node;
    }
    else {	//在其余位置包括最后的位置插入node
        //while (i < pos - 1) {	//找第pos位置的前一个节点
        //	p = p->next;
        //	i++;
        //}
        //直接调用前面写的函数
        LNode* pre = getNode(L, pos - 1);

        node->next = pre->next;
        pre->next = node;
    }
}

//删除第pos位置的节点,并且返回被删除节点的元素值e
//void DeleteElem(LinkedList& L, int pos, ElemType& e) {
//	//pos合法范围为:[1,length]
//	if (pos<1 || pos>length(L)) {
//		return;
//	}
//
//	int i = 1;
//	int j = 1;
//	LNode* p = L;	//p为遍历指针
//	LNode* pr = L;	//pr为遍历指针
//	if (pos == 1) {
//		e = L->data;
//		L = L->next;
//	}
//	else {
//		while (i < pos) {	//找到第pos位置的节点
//			i++;
//			p = p->next;
//		}
//		while (j < pos - 1) {	//找到第pos-1位置的节点
//			j++;
//			pr = pr->next;
//		}
//		e = p->data;
//		pr->next=p->next;
//	}
//}

void DeleteElem(LinkedList& L, int pos, ElemType& e) {
    //pos合法范围为:[1,length]
    if (pos<1 || pos>length(L)) {
        return;
    }
    LNode* p = getNode(L, pos);	//找到pos位置的节点,要删除的就是它
    if (pos == 1) {	//删除第一个位置的节点
        L = p->next;
    }
    else {	//删除其他位置的节点
        LNode* pr=getNode(L, pos-1);	//找到pos-1位置的节点
        pr->next = p->next;
    }
    e = p->data;
    free(p);	//释放空间
}

Client.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "LinkedList.h"
#include<stdio.h>
#include<stdlib.h>

int main() {
    LinkedList L;	//创建单链表
    initLinkedList(L);	//初始化单链表
    //createListByHead(L);	//头插法创建单链表(输出与输入顺序相反)
    createListByTail(L);	//尾插法创建单链表(输出与输入顺序一致)
    printLinkedList(L);	//打印单链表
    int res = -1;
    DeleteElem(L, 4, res);
    printf("删除的节点元素值为:%d\n",res);
    printLinkedList(L);	//打印单链表

    //insertElem(L, 5, 100);
    //printf("插入之后的单链表为:\n");
    //printLinkedList(L);	//打印单链表

    /*LNode* cur=getNode(L, 2);
    printf("第二个节点的值为%d\n", cur->data);*/
    //printf("表长为:%d\n", length(L));
    return 0;
}

二、带头结点的单链表基本操作

(1)初始化

🌽代码

cpp 复制代码
//初始化(需要申请一个头结点,让L指向它)
void initDummyLinkedList(DummyLinkedList& L) {
    L = (LNode*)malloc(sizeof(LNode));
    L->data = 0;	//假设头结点数据域用于存放表长信息
    L->next = NULL;
}

头指针始终指向第一个节点,不论是否有头结点。

(2)头插法创建单链表

🌽代码

cpp 复制代码
//头插法创建带头结点的单链表
void createDummyListByHead(DummyLinkedList& L) {
    ElemType x;
    scanf("%d", &x);
    while (x != 999) {
        LNode* node = (LNode*)malloc(sizeof(LNode));
        node->data = x;
        node->next = L->next;
        L->next = node;
        L->data++;	//创建一个就记录一个,表长加一
        scanf("%d", &x);
    }
}

🍺测试结果

(3)尾插法创建单链表

🌽代码

cpp 复制代码
//尾插法创建带头结点的单链表
void createDummyListByTail(DummyLinkedList& L) {
    ElemType x;
    scanf("%d", &x);
    LNode* tail = L;	//尾指针,最开始指向头结点
    while (x != 999) {
        LNode* node = (LNode*)malloc(sizeof(LNode));
        node->data = x;
        node->next = NULL;

        tail->next = node;
        tail = node;	//tail总是指向最后一个节点
        L->data++;	//表长加一
        scanf("%d", &x);
    }
}

带头结点的单链表 ,头插法和尾插法都不需要额外判断是否创建的是第一个节点

🍺测试结果

(4)打印

🌽代码

cpp 复制代码
//打印带头结点的单链表(不打印第一个节点也就是头结点的数据)
void printDummyLinkedList(DummyLinkedList L) {
    LNode* p = L->next;	//从第一个有真实意义的节点开始打印
    while (p != NULL) {
        printf("%d->", p->data);
        p = p->next;
    }
    printf("\n");
}

(5)按位查找

🌽代码

cpp 复制代码
//返回第pos位置上的节点
LNode* getNode(DummyLinkedList L, int pos) {
    //pos合法范围是[0,表长],注意可以取0,我们输出头结点即可
    //第一个位置是从真实意义的结点开始计算的
    if (pos<0 || pos>L->data) {
        return NULL;
    }
    int i = 0;	//注意这里i从0开始
    LNode* p = L;
    while (p != NULL&& i<pos) {
        p = p->next;
        i++;
    }
    return p;
}

(6)第pos位置上插入元素e

🌽代码

cpp 复制代码
//第pos位置上插入元素e
void insertElem(DummyLinkedList& L, int pos, ElemType e) {
    //pos的合法范围是:[1,表长+1]
    if (pos<1 || pos>L->data + 1) {
        return;
    }

    LNode* node = (LNode*)malloc(sizeof(LNode));
    node->data = e;
    node->next = NULL;

    LNode* pr = getNode(L, pos - 1);	//找到要插入位置的前一个位置
    node->next = pr->next;
    pr->next = node;
    L->data++;	//维护表长
}

🍺测试结果

(7)删除第pos位置的元素节点

🌽代码

cpp 复制代码
//删除第pos位置的元素节点,并用e返回被删除节点的元素值
void deleteElem(DummyLinkedList& L, int pos, ElemType& e) {
    //pos合法范围是[1,表长]
    if (pos<1 || pos>L->data) {
        return;
    }
    LNode* pr = getNode(L, pos - 1);	//pos-1位置上面的节点
    LNode* p = getNode(L, pos);	//pos位置上面的节点,要删除的元素
    pr->next = p->next;
    e=p->data;
    L->data--;	//维护表长
    free(p);	//释放空间
}

🍺测试结果

(8)求表长

🌽代码

cpp 复制代码
//求表长
int length(DummyLinkedList& L) {
    return L->data;
}

(9)整体代码

DummyLinkedList.h

cpp 复制代码
#pragma once
typedef int ElemType;
typedef struct LNode{
    ElemType data;	//数据域
    LNode* next;	//指针域
}LNode,*DummyLinkedList;

//初始化(需要申请一个头结点,让L指向它)
void initDummyLinkedList(DummyLinkedList& L);

//头插法创建带头结点的单链表
void createDummyListByHead(DummyLinkedList& L);

//打印带头结点的单链表
void printDummyLinkedList(DummyLinkedList L);

//尾插法创建带头结点的单链表
void createDummyListByTail(DummyLinkedList& L);

//返回第pos位置上的节点
LNode* getNode(DummyLinkedList L, int pos);

//第pos位置上插入元素e
void insertElem(DummyLinkedList& L, int pos, ElemType e);

//删除第pos位置的元素节点,并用e返回被删除节点的元素值
void deleteElem(DummyLinkedList& L, int pos, ElemType& e);

//求表长
int length(DummyLinkedList& L);

DummyLinkedList.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "DummyLinkedList.h"
#include<stdio.h>
#include<stdlib.h>

//初始化(需要申请一个头结点,让L指向它)
void initDummyLinkedList(DummyLinkedList& L) {
    L = (LNode*)malloc(sizeof(LNode));
    L->data = 0;	//假设头结点数据域用于存放表长信息
    L->next = NULL;
}

//头插法创建带头结点的单链表
void createDummyListByHead(DummyLinkedList& L) {
    ElemType x;
    scanf("%d", &x);
    while (x != 999) {
        LNode* node = (LNode*)malloc(sizeof(LNode));
        node->data = x;
        node->next = L->next;
        L->next = node;
        L->data++;	//创建一个就记录一个,表长加一
        scanf("%d", &x);
    }
}

//打印带头结点的单链表(不打印第一个节点也就是头结点的数据)
void printDummyLinkedList(DummyLinkedList L) {
    LNode* p = L->next;	//从第一个有真实意义的节点开始打印
    while (p != NULL) {
        printf("%d->", p->data);
        p = p->next;
    }
    printf("\n");
}

//尾插法创建带头结点的单链表
void createDummyListByTail(DummyLinkedList& L) {
    ElemType x;
    scanf("%d", &x);
    LNode* tail = L;	//尾指针,最开始指向头结点
    while (x != 999) {
        LNode* node = (LNode*)malloc(sizeof(LNode));
        node->data = x;
        node->next = NULL;

        tail->next = node;
        tail = node;	//tail总是指向最后一个节点
        L->data++;	//表长加一
        scanf("%d", &x);
    }
}

//返回第pos位置上的节点
LNode* getNode(DummyLinkedList L, int pos) {
    //pos合法范围是[0,表长],注意可以取0,我们输出头结点即可
    //第一个位置是从真实意义的结点开始计算的
    if (pos<0 || pos>L->data) {
        return NULL;
    }
    int i = 0;	//注意这里i从0开始
    LNode* p = L;
    while (p != NULL&& i<pos) {
        p = p->next;
        i++;
    }
    return p;
}

//第pos位置上插入元素e
void insertElem(DummyLinkedList& L, int pos, ElemType e) {
    //pos的合法范围是:[1,表长+1]
    if (pos<1 || pos>L->data + 1) {
        return;
    }

    LNode* node = (LNode*)malloc(sizeof(LNode));
    node->data = e;
    node->next = NULL;

    LNode* pr = getNode(L, pos - 1);	//找到要插入位置的前一个位置
    node->next = pr->next;
    pr->next = node;
    L->data++;	//维护表长
}

//删除第pos位置的元素节点,并用e返回被删除节点的元素值
void deleteElem(DummyLinkedList& L, int pos, ElemType& e) {
    //pos合法范围是[1,表长]
    if (pos<1 || pos>L->data) {
        return;
    }
    LNode* pr = getNode(L, pos - 1);	//pos-1位置上面的节点
    LNode* p = getNode(L, pos);	//pos位置上面的节点,要删除的元素
    pr->next = p->next;
    e=p->data;
    L->data--;	//维护表长
    free(p);	//释放空间
}

//求表长
int length(DummyLinkedList& L) {
    return L->data;
}

Client.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"DummyLinkedList.h"
#include<stdio.h>
#include<stdlib.h>

int main() {
    DummyLinkedList L;	//创建带头结点的单链表
    initDummyLinkedList(L);	//初始化
    //createDummyListByHead(L);	//头插法
    createDummyListByTail(L);	//尾插法创建
    printDummyLinkedList(L);	//打印

    //insertElem(L, 5, 100);		//插入

    ElemType x;
    deleteElem(L, 8, x);

    printDummyLinkedList(L);	//打印链表
    printf("被删除的元素值为:%d\n", x);

    return 0;
}

三、循环单链表(带头结点)

  • 带头结点的循环单链表 :尾指针指向头结点(没有真实意义的节点),头结点后面是首节点

  • 不带头结点的循环单链表 :尾指针指向首节点(有真实意义的节点)

【相关操作】

带头结点的循环单链表 ,若是要**删除最后一个节点**,时间复杂度就不是O(1)了,因为**必须要找到最后节点的前面一个**,让它指向L.head,然后删除最后一个节点。

但是,在**最后一个位置插入节点**,只需要让尾指针tail指向它即可,它再指向头指针。

(1)结构体

🌽代码

cpp 复制代码
//结构体
typedef struct LNode{	//仅代表一个节点
	ElemType data;	//数据域
	LNode* next;	//指针域
	LNode* tail;	//每一个都加尾节点,会导致空间的浪费
}LNode;

typedef struct {	//代表一个链表
	LNode* head;	//头结点
	LNode* tail;	//尾节点
	int length;	//长度
}LinkedList;

⭐注意

若每一个都加尾节点,会导致空间的浪费。它只有一个节点是尾节点。

(2)初始化循环单链表

🌱分析

🌽代码

cpp 复制代码
//初始化循环单链表(带头结点)
void initLinkedList(LinkedList& L) {
	L.head = (LNode*)malloc(sizeof(LNode));	//创建一个头结点
	L.tail = L.head;	//尾指针tail初始指向head
	L.tail->next = L.head;	//最后一个节点的后继指向头(循环链表)
	L.length = 0;	//表长初始为0
}

(3)头插法创建循环单链表

🌱分析

<1> 创建的是第一个节点

<2> 创建的不是第一个节点

注意,只有创建第一个节点的时候才需要动tail指针,因为头插法会导致第一个创建的节点一直是尾节点(一直在往头部插入节点),所以后面创建的节点不需要再动tail指针。

【写法1】

🌽代码

cpp 复制代码
//头插法创建循环单链表(带头结点)
void createLinkedListWithHead(LinkedList& L) {
    ElemType x;
    scanf("%d", &x);
    while (x != 999) {
        LNode* node = (LNode*)malloc(sizeof(LNode));
        node->data = x;
        if (L.head==L.tail) {	//创建的是第一个节点
            node->next = L.head->next;	//最开始L.head->next就是head
            L.head->next = node;
            //注意tail需要移动到最后的节点处(创建第一个节点的时候才动它)
            L.tail = node;	//创建的第一个节点将是最后一个节点
        }
        else {	//创建的不是第一个节点
            //头插法,第一个创建的节点会是最后一个节点,往后创建的节点无需再动tail
            node->next = L.head->next;
            L.head->next = node;
        }
        L.length++;	//表长加一
        scanf("%d", &x);
    }
}

【写法2】采用这种写法

🌽代码

cpp 复制代码
void createLinkedListWithHead(LinkedList& L) {
    ElemType x;
    scanf("%d", &x);
    while (x != 999) {
        LNode* node = (LNode*)malloc(sizeof(LNode));
        node->data = x;
        node->next = L.head->next;
        L.head->next = node;
        if (L.head == L.tail) {	//只有创建的是第一个节点,才需要动tail
            L.tail = node;	//之后创建的节点都在前面
        }
        L.length++;	//表长加一
        scanf("%d", &x);
    }
}

🍺测试结果

(4)尾插法创建循环单链表

🌱分析

每次新创建的作为尾节点,指向头节点。

🌽代码

cpp 复制代码
//尾插法创建循环单链表
void createLinkedListWithTail(LinkedList& L) {
    ElemType x;
    scanf("%d", &x);
    while (x != 999) {
        LNode* node = (LNode*)malloc(sizeof(LNode));
        node->data = x;

        //无需判断创建的是不是第一个节点,每次让新来的作为尾节点,指向头结点
        node->next = L.head;
        L.tail->next = node;
        L.tail = node;

        L.length++;
        scanf("%d", &x);
    }
}

🍺测试结果

(5)打印

🌽代码

cpp 复制代码
//打印链表
void printLinkedList(LinkedList L) {
    LNode* p = L.head->next;	//遍历指针p指向第一个有真实意义的节点
    while (p != L.head) {	//当p重新回到头结点的时候,就说明已经遍历好了一轮
        printf("%d->", p->data);
        p = p->next;	//p指针后移
    }
    printf("\n");	//换行
}

(6)返回第pos位置上的节点

🌱分析

【案例1】

【案例2】

🌽代码

cpp 复制代码
//返回第pos位置上的节点
LNode* getNode(LinkedList L, int pos) {
    //pos合法范围:[0,表长]
    if (pos<0 || pos>L.length) {
        return NULL;
    }

    if (pos == 0) {	//0号位置是头结点,没有存真实意义的数据
        return L.head;	//返回头结点
    }

    int i = 1;	
    LNode* p = L.head->next; //p从首节点(第一个有真实意义的节点)开始往后遍历
    while (i < pos && p != L.head) {	
        p = p->next;
        i++;
    }
    //p若是等于头结点说明没找到第pos位置,返回空即可;否则返回p
    return p == L.head ? NULL : p;	
}

(7)向表尾插入元素e

🌽代码

cpp 复制代码
//向表尾插入元素e
void insertElemToTail(LinkedList& L, ElemType e) {
    LNode* node = (LNode*)malloc(sizeof(LNode));	//申请空间
    node->data = e;
    L.tail->next = node;
    node->next = L.head;
    L.tail=node;
    L.length++;
}

🍺测试结果

(8)从cur节点处遍历循环单链表

🌽代码

cpp 复制代码
//从cur节点处遍历循环单链表
void printLinkedList2(LNode* cur, LinkedList L) {
    LNode* p = cur;		//记录cur结点的位置
    for (int i = 0; i < L.length+1; i++) {	//因为有头节点,所以需要遍历到表长
        if (p != L.head) {	//头节点没有元素,无需打印
            printf("%d->", p->data);
        }
        p = p->next;
    }
    printf("\n");
}

🍺测试结果

又如:

(9)整体代码

LinkedList.h

cpp 复制代码
#pragma once
typedef int ElemType;

//结构体
typedef struct LNode{	//代表一个节点
    ElemType data;	//数据域
    LNode* next;	//指针域
}LNode;

typedef struct {	//代表链表
    LNode* head;	//头结点
    LNode* tail;	//尾节点
    int length;	//长度
}LinkedList;

//初始化单链表
void initLinkedList(LinkedList& L);

//头插法创建循环单链表
void createLinkedListWithHead(LinkedList& L);

//尾插法创建循环单链表
void createLinkedListWithTail(LinkedList& L);

//返回第pos位置上的节点
LNode* getNode(LinkedList L, int pos);

//向表尾插入元素e
void insertElemToTail(LinkedList& L, ElemType e);

//打印链表
void printLinkedList(LinkedList L);

//从cur节点处遍历循环单链表
void printLinkedList2(LNode* cur, LinkedList L);

LinkedList.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "LinkedList.h"
#include<stdio.h>
#include<stdlib.h>

//初始化单链表(带头结点)
void initLinkedList(LinkedList& L) {
    L.head = (LNode*)malloc(sizeof(LNode));	//创建一个头结点
    L.tail = L.head;	//尾指针tail初始指向head
    L.tail->next = L.head;	//最后一个节点的后继指向头(循环链表)
    L.length = 0;	//表长初始为0
}

//头插法创建循环单链表(带头结点)
//void createLinkedListWithHead(LinkedList& L) {
//	ElemType x;
//	scanf("%d", &x);
//	while (x != 999) {
//		LNode* node = (LNode*)malloc(sizeof(LNode));
//		node->data = x;
//		if (L.head==L.tail) {	//创建的是第一个节点
//			node->next = L.head->next;	//最开始L.head->next就是head
//			L.head->next = node;
//			//注意tail需要移动到最后的节点处(创建第一个节点的时候才动它)
//			L.tail = node;	//创建的第一个节点将是最后一个节点
//		}
//		else {	//创建的不是第一个节点
//			//头插法,第一个创建的节点会是最后一个节点,往后创建的节点无需再动tail
//			node->next = L.head->next;
//			L.head->next = node;
//		}
//		L.length++;	//表长加一
//		scanf("%d", &x);
//	}
//}

void createLinkedListWithHead(LinkedList& L) {
    ElemType x;
    scanf("%d", &x);
    while (x != 999) {
        LNode* node = (LNode*)malloc(sizeof(LNode));
        node->data = x;
        node->next = L.head->next;
        L.head->next = node;
        if (L.head == L.tail) {	//只有创建的是第一个节点,才需要动tail
            L.tail = node;	//之后创建的节点都在前面
        }
        L.length++;	//表长加一
        scanf("%d", &x);
    }
}

//尾插法创建循环单链表
void createLinkedListWithTail(LinkedList& L) {
    ElemType x;
    scanf("%d", &x);
    while (x != 999) {
        LNode* node = (LNode*)malloc(sizeof(LNode));
        node->data = x;

        //无需判断创建的是不是第一个节点,每次让新来的作为尾节点,指向头结点
        L.tail->next = node;
        node->next = L.head;
        L.tail = node;

        L.length++;
        scanf("%d", &x);
    }
}

//返回第pos位置上的节点
LNode* getNode(LinkedList L, int pos) {
    //pos合法范围:[0,表长]
    if (pos<0 || pos>L.length) {
        return NULL;
    }

    if (pos == 0) {
        return L.head;
    }

    int i = 1;
    LNode* p = L.head->next; 
    while (i < pos && p != L.head) {	
        p = p->next;
        i++;
    }
    //p若是等于头结点说明没找到第pos位置,返回空即可;否则返回p
    return p == L.head ? NULL : p;	
}

//向表尾插入元素e
void insertElemToTail(LinkedList& L, ElemType e) {
    LNode* node = (LNode*)malloc(sizeof(LNode));
    node->data = e;
    L.tail->next = node;
    node->next = L.head;
    L.tail=node;
    L.length++;
}

//打印链表
void printLinkedList(LinkedList L) {
    LNode* p = L.head->next;	//遍历指针p指向第一个有真实意义的节点
    while (p != L.head) {
        printf("%d->", p->data);
        p = p->next;
    }
    printf("\n");
}

//从cur节点处遍历循环单链表
void printLinkedList2(LNode* cur, LinkedList L) {
    LNode* p = cur;
    for (int i = 0; i < L.length+1; i++) {	//因为有头节点,所以需要遍历到表长
        if (p != L.head) {	//头节点没有元素,无需打印
            printf("%d->", p->data);
        }
        p = p->next;
    }
    printf("\n");
}

Client.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "LinkedList.h"
#include<stdio.h>
#include<stdlib.h>

int main() {
    LinkedList L;
    initLinkedList(L);
    //createLinkedListWithHead(L);//头插法创建
    createLinkedListWithTail(L);	//尾插法创建
    printLinkedList(L);	//打印

    //insertElemToTail(L, 100);	//表尾插入100
    //printLinkedList(L);	//打印
    for (int i = 1; i < 10; i++) {
        LNode* p = getNode(L, i);	//返回第i个位置的节点
        printLinkedList2(p, L);	//从链表第i个节点处打印链表
    }

    return 0;
}

四、双链表

(1)介绍

【单链表】

【双链表】

若是删除4号结点双链表 可以直接找到前驱结点,让其指向空即可,时间复杂度为O(1)

单链表 需要遍历找到前驱的3号结点,然后让它指向空,时间复杂度为O(n)

如下图:

(2)结构体

🌽代码

cpp 复制代码
//结构体
typedef struct DNode {
	ElemType data;	//数据域
	DNode* next;	//用来存放后继结点的指针
	DNode* prior;	//用来存放前驱结点的指针
}DNode,*DLinkedList;

(3)初始化带头结点的双链表

假设带头结点

🌽代码

cpp 复制代码
//双链表的初始化
void initDLinkedList(DLinkedList& L) {
	L = (DNode*)malloc(sizeof(DNode));	//创建一个头结点
	L->next = NULL;
	L->prior = NULL;
	L->data = 0;	//假设用头结点的数据域来存放表长
}

(4)打印双链表

🌽代码

cpp 复制代码
//打印双链表
void printDList(DLinkedList L) {
	DNode* p = L->next;	//从第一个有真实意义的节点开始遍历输出
	while (p) {	//p不空的时候一直遍历
		printf("%d->", p->data);
		p = p->next;
	}
	printf("\n");	//换行
}

(5)头插法创建双链表

🌱分析

🌽代码

cpp 复制代码
//头插法创建双链表
void createDListWithHead(DLinkedList& L) {
	ElemType x;
	scanf("%d", &x);
	while (x != 999) {
		DNode* node = (DNode*)malloc(sizeof(DNode));
		node->data = x;
		node->next = NULL;
		node->prior = NULL;

		node->next = L->next;
		if (L->next != NULL) {	//创建的node不是第一个节点
			L->next->prior = node;
		}
		node->prior = L;
		L->next = node;

		L->data++;
		scanf("%d", &x);
	}
}

🍺测试结果

(6)尾插法创建双链表

🌱分析

头插法需要看插入的是否是第一个结点,而尾插法不需要。

若创建的不是第一个结点,代码和上面一样。

🌽代码

cpp 复制代码
//尾插法创建双链表
void createDListWithTail(DLinkedList& L) {
	ElemType x;
	scanf("%d", &x);
	DNode* tail = L;	//尾指针初始指向头节点

	while (x != 999) {
		DNode* node = (DNode*)malloc(sizeof(DNode));
		node->data = x;

		tail->next = node;
		node->prior = tail;
		tail = node;
		tail->next = NULL;

		L->data++;	//表长加一
		scanf("%d", &x);	//继续输入下一个
	}
}

🍺测试结果

(7)返回第pos位置上的结点

🌽代码

cpp 复制代码
//返回第pos位置上的结点
DNode* getNode(DLinkedList L, int pos) {
	if (pos<0 || pos>L->data) {	//非法位置直接返回NULL
		return NULL;
	}

	//遍历链表
	int i = 0;
	DNode* p = L;	//遍历指针P初始指向表头L
	while (p!=NULL && i < pos) {
		i++;
		p = p->next;
	}
	return p;
}

(8)在第pos个位置插入元素e

🌱分析

🌽代码

cpp 复制代码
//在第pos个位置插入元素e
void insertElem(DLinkedList& L, int pos, ElemType e) {
	DNode* pre = getNode(L, pos - 1);	//找到第pos位置的前驱结点
	DNode* node = (DNode*)malloc(sizeof(DNode));	//创建要插入的结点
	node->data = e;

	//若不是在最后一个结点位置上面插入
	if (pos != L->data+1) {	//最后一个位置是在表长加一的位置
		pre->next->prior = node;
	}
	node->next = pre->next;
	node->prior = pre;
	pre->next = node;

	L->data++;
}

🍺测试结果

(9)删除第pos位置上的元素

🌱分析

🌽代码

cpp 复制代码
//删除第pos位置上的元素,用e接收被删除的元素值
void deleteElem(DLinkedList& L, int pos, ElemType& e) {
	//pos合法性这里省略
	DNode* pre=getNode(L, pos-1);	//找到第pos位置的前一个结点
	DNode* removed = getNode(L, pos);	//找到第pos位置的结点
	if (pos==L->data) {	//当pos等于表长的时候,删除的是最后一个结点
		pre->next = NULL;
	}
	else {	//删除的不是最后一个结点
		pre->next = removed->next;
		removed->next->prior = pre;
	}
	L->data--;	//表长减一
	e = removed->data;
	free(removed);	//释放空间
}

🍺测试结果


(10)整体代码

DLinkedList.h

cpp 复制代码
#pragma once
typedef int ElemType;

//结构体
typedef struct DNode {
	ElemType data;	//数据域
	DNode* next;	//用来存放后继结点的指针
	DNode* prior;	//用来存放前驱结点的指针(单链表没有)
}DNode,*DLinkedList;
//DNode强调的是一个结点,DLinkedList强调的是一个链表,语义不同罢了
//DNode* p与DLinkedList p一样

//双链表的初始化
void initDLinkedList(DLinkedList& L);

//头插法创建双链表
void createDListWithHead(DLinkedList& L);

//尾插法创建双链表
void createDListWithTail(DLinkedList& L);

//返回第pos位置上的结点
DNode* getNode(DLinkedList L, int pos);

//在第pos个位置插入元素e
void insertElem(DLinkedList& L, int pos, ElemType e);

//删除第pos位置上的元素,用e接收被删除的元素值
void deleteElem(DLinkedList& L, int pos, ElemType& e);

//打印双链表
void printDList(DLinkedList L);
 

DLinkedList.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "DLinkedList.h"
#include<stdio.h>
#include<stdlib.h>

//双链表的初始化
void initDLinkedList(DLinkedList& L) {
	L = (DNode*)malloc(sizeof(DNode));	//创建一个头结点
	L->next = NULL;
	L->prior = NULL;
	L->data = 0;	//假设用头结点的数据域来存放表长
}

//头插法创建双链表
void createDListWithHead(DLinkedList& L) {
	ElemType x;
	scanf("%d", &x);
	while (x != 999) {
		DNode* node = (DNode*)malloc(sizeof(DNode));
		node->data = x;
		node->next = NULL;
		node->prior = NULL;

		node->next = L->next;
		if (L->next != NULL) {	//创建的node不是第一个节点
			L->next->prior = node;

		}
		node->prior = L;
		L->next = node;

		L->data++;
		scanf("%d", &x);
	}
}

//尾插法创建双链表
void createDListWithTail(DLinkedList& L) {
	ElemType x;
	scanf("%d", &x);
	DNode* tail = L;	//尾指针初始指向头节点

	while (x != 999) {
		DNode* node = (DNode*)malloc(sizeof(DNode));
		node->data = x;

		tail->next = node;
		node->prior = tail;
		tail = node;
		tail->next = NULL;

		L->data++;	//表长加一
		scanf("%d", &x);	//继续输入下一个
	}
}

//返回第pos位置上的结点
DNode* getNode(DLinkedList L, int pos) {
	if (pos<0 || pos>L->data) {	//非法位置直接返回NULL
		return NULL;
	}

	//遍历链表
	int i = 0;
	DNode* p = L;	//遍历指针P初始指向表头L
	while (p!=NULL && i < pos) {
		i++;
		p = p->next;
	}
	return p;
}

//在第pos个位置插入元素e
void insertElem(DLinkedList& L, int pos, ElemType e) {
	DNode* pre = getNode(L, pos - 1);	//找到第pos位置的前驱结点
	DNode* node = (DNode*)malloc(sizeof(DNode));	//创建要插入的结点
	node->data = e;

	//若不是在最后一个结点位置上面插入
	if (pos != L->data+1) {	//最后一个位置是在表长加一的位置
		pre->next->prior = node;
	}
	node->next = pre->next;
	node->prior = pre;
	pre->next = node;

	L->data++;
}

//删除第pos位置上的元素,用e接收被删除的元素值
void deleteElem(DLinkedList& L, int pos, ElemType& e) {
	//pos合法性这里省略
	DNode* pre=getNode(L, pos-1);	//找到第pos位置的前一个结点
	DNode* removed = getNode(L, pos);	//找到第pos位置的结点
	if (pos==L->data) {	//当pos等于表长的时候,删除的是最后一个结点
		pre->next = NULL;
	}
	else {	//删除的不是最后一个结点
		pre->next = removed->next;
		removed->next->prior = pre;
	}
	L->data--;	//表长减一
	e = removed->data;
	free(removed);	//释放空间
}

//打印双链表
void printDList(DLinkedList L) {
	DNode* p = L->next;	//从第一个有真实意义的节点开始遍历输出
	while (p) {	//p不空的时候一直遍历
		printf("%d->", p->data);
		p = p->next;
	}
	printf("\n");	//换行
}

Client.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "DLinkedList.h"
#include<stdio.h>
#include<stdlib.h>
int main() {
	DLinkedList L;	//定义双链表
	initDLinkedList(L);	//初始化双链表
	//createDListWithHead(L);	//头插法创建双链表
	createDListWithTail(L);	//尾插法创建双链表
	printDList(L);	//打印
	//insertElem(L,5,1000);
	//printDList(L);	//打印

	ElemType x;
	deleteElem(L, 5, x);
	printDList(L);	//打印
	printf("被删除的元素值为:%d\n", x);
	return 0;
}
相关推荐
小龙报3 小时前
【算法通关指南:数据结构与算法篇】二叉树相关算法题:1.二叉树深度 2.求先序排列
c语言·开发语言·数据结构·c++·算法·贪心算法·动态规划
皮卡蛋炒饭.3 小时前
钻石收集者&是7倍数的最长子序列&Zuma
数据结构·算法·排序算法
仰泳的熊猫3 小时前
题目1529:蓝桥杯算法提高VIP-摆花
数据结构·c++·算法·蓝桥杯
小糯米6014 小时前
C++ 树
数据结构·c++·算法
掘根4 小时前
【C++STL】红黑树(RBTree)
数据结构·c++·算法
Zik----4 小时前
Leetcode20 —— 有效的括号(栈解法)
数据结构
苦藤新鸡4 小时前
64 搜索平移递增数组中的元素
数据结构·算法
Vic101015 小时前
链表算法三道
java·数据结构·算法·链表
二年级程序员5 小时前
一篇文章掌握“栈”
c语言·数据结构