复试前数据结构知识点复盘【第二章线性表之链表】

一、单链表:

【前提,本文用的所有代码都是抄的,我懒得写,所以只是对代码的解释,勿喷,如果有错可以告诉我,如果侵权,我可以删掉代码,嘿嘿嘿】

链表:还用多说吗,肯定就单链表、双链表、循环链表。

每种链表又分为带头节点和不带头节点的,为啥要区分带不带头节点,因为带头节点的会方便很多,无论后续的增、删、改、查都会简单很多,后续会说明。

因为前面一篇顺序表太简单了,所以我就没敲代码上来,链表对于初学者会难理解一点点,所以我还是拿代码进行说明吧,不过我会抄一下其他人的代码,主要起一个说明作用,方便自己理解,也方便有人看我这篇文章理解。

0、定义:

// 定义链表的节点
typedef struct node
{
    int data;              //这个节点要保存什么数据
    struct node *next;     //这个节点指向下一个节点
}Node,*Link;               //定义别名 Node, *Link

看见上面这个代码蒙蔽不,上面这个就是对单链表的节点的定义,我们来对比一下顺序表的定义:

//定义顺序表

typedef struct
{
    int length;//当前顺序表长度
    int Maxsize;//顺序表最大长度
    int* data;//定义顺序表中元素类型的数组指针
}SqList;

是不是发现两个特别像,但是又不一样,废话,都是用结构体定义的,肯定像。区别也有,顺序表中的这个指针是单纯的指针,也就是并不是自己定义的结构体指针。换句话说就是,一个是普通指针,一个是结构体指针。你要问我这个指针有什么用,那我们来看单链表的初始化和顺序表的初始化。(为什么我扯上顺序表,一方面补一下上一篇的代码,一方面可以看一下两者的比较,至于我后面讲循环链表和双链表的时候就是在单链表上做延伸,不再做比较了)

1、初始化:

链表初始化:

// 带头结点的单链表初始化函数  
ListNode* initListWithHead() {  
    // 分配头结点空间  
    ListNode *head = (ListNode*)malloc(sizeof(ListNode));  
    if (head == NULL) {  
        printf("Memory allocation failed!\n");  
        exit(1);  
    }  
    // 初始化头结点  
    head->val = 0; // 头结点不存储数据,可以设置为任意值  
    head->next = NULL;  
    return head;  
}  
  
// 不带头结点的单链表初始化函数  
ListNode* initListWithoutHead() {  
    // 初始时链表为空,返回NULL  
    return NULL;  

顺序表初始化:

void InitList(SqList &L)
{
    L.data = (int *)malloc(InitSize*sizeof(int));//用malloc函数申请一片空间放我表中的数据
    L.length = 0;//顺序表中现在没有值,那不就是空表咯,不是0还能是什么
    L.Maxsize = InitSize;//最大长度不定义的话,鬼知道我能插入多少数据进去
}

两者的比较都写在注释里了,可以自己看,看得懂就看,看不懂就看书。

2、插入:

(包含带头节点和不带头节点的头插法、尾插法,再补一下昨天的顺序表插入)

插入操作:

链表不同与顺序表,顺序表就顺着插就好了,链表有头插法和尾插法,啥叫头插法,每次插入的新元素都在头节点之后的第一个元素,就叫头插,啥叫尾插,每次插入的新元素都在尾巴,就交尾插。

带头节点的尾插法代码:

struct node* insert_node(struct node* head, int pos, int data)//head是我传入的头节点,pos是表长,data是我要插入的数据
{
    NODE* p = head, * q = head;
    NODE* new_node = (NODE*)malloc(sizeof(NODE));
    new_node->data = data;
    for (int i = 1; i <= pos; i++)//循环节束后q指向表尾,p指向空
    {
        q = p;
        p = p->next;
    }
    if (p == NULL)
    {
        p->new_node;
        q->next = p;
        new_node->next = NULL;
    }
    return head;
}

带头节点的头插法代码:

NODE* insert_node_at_head(NODE* head, int data) {  
    NODE* new_node = (NODE*)malloc(sizeof(NODE));  
    if (new_node == NULL) {  
        //分配失败,那岂不是没节点 ,这里写报错代码
    }  
    new_node->data = data;  
    new_node->next = head->next; // 将新节点的next指向原头节点的后一个节点 
    head->next = new_node; // 更新头节点为新节点  
    return head; // 返回新的头节点  

不带头节点的尾插:
// 尾插法  
Node* insertAtTail(Node* head, int data) {  
    Node* newNode = createNode(data);  //这里创建节点的函数就没用malloc函数,其实都差不多,自己想怎么改就怎么改,毕竟代码是自己写的
    if (head == NULL) {  
        // 如果链表为空,新创建的节点就是首元节点
        head = newNode;  
    } else {  
        // 否则,找到尾节点并插入新节点  
        Node* current = head;  
        while (current->next != NULL) {  
            current = current->next;  
        }  
        current->next = newNode;  
    }  
    return head; // 返回首元节点 
}  

不带头节点的头插:

// 头插法  
Node* insertAtHead(Node* head, int data) {  

if (head == NULL) {  
        // 如果链表为空,新创建的节点就是首元节点
        head = newNode;  
    } else {
    Node* newNode = createNode(data);  
    newNode->next = head; // 新节点的next指向原首元节点  
    head = newNode; // 更新首元节点为新节点  

   }
    return head; // 返回新的首元节点  
}  

以上就是带头节点和不带头节点的两种代码比较,相同点:都是要传入头节点,尾插法可以传入表长直接然后用for循环找到尾节点,也可以不传入表长直接while找到,代码不一致,这里也告诉大家,不用拘泥于代码写法,能实现目的的写法就是最好的写法

接下来就是顺序表的插入:

bool ListInsret(SqList &L)
{
    int i, e;
    printf("请输入要插入顺序表的元素和元素位置:");
    scanf("%d %d", &e, &i);
    if (i<1 || i>L.length + 1)//判断元素下标是否越界
        return false;
    if (L.length > L.Maxsize)//判断顺序表存储空间是否满了
        return false;
    for (int j = L.length; j >= i; j--)
    {
        L.data[j] = L.data[j-1];//从后往前逐个后移元素
    }
    L.data[i-1] = e;//将新元素放入下标为i-1的位置
    L.length++;//表长+1
    printf("插入的元素是%d,插入的位置是%d\n", e, i);
    return true;
}
因为顺序表的实质是一个数组,所以有下标索引,插入一个元素都会涉及到元素的迁移(每次都往最后一个元素的后一个位置插入例外哈,别杠精)正因为有下标索引,所以查询方便,插入删除不方便。

3、查找:

(链表按值查找返回地址,顺序表按值查找返回下标):

// 链表取值查询  
int getValueWithHead(Node* head, int e) {  
    if (head == NULL || position < 1) {  
        printf("Invalid head or position!\n");  
        return -1; // 都没有头节点你找个锤子
    }  
    Node* current = head->next; // 跳过头节点  //(如果是不带头节点的就不后面的箭头和next去掉)
    int index = 1;  
    while (current != NULL) {  
        if (current->data==e) {  
            return current; // 找到指定位置,返回指针,也就是地址  
        }  
        current = current->next; 
    } 
    return -1; // 说明超出范围都找不到这个值
}

// 顺序表查询  

void LocateElem(SqList &L)
{
    int e;
    int k = 1;
    printf("输入你要查找的元素:");
    scanf("%d", &e);
    for (int i = 0; i < L.length; i++)
    if (L.data[i] == e)
    {
        printf("找到了,是第%d个元素\n", i + 1);
        k = 0;
        break;
    }
    if (k)
        printf("找不到元素%d\n", e);
}

4、删除:

删除肯定要考虑带头节点和不带头节点,比如带头节点的,是不是删到只剩头节点就算删完了,但是不带头节点的要删除到空才行,顺序表的删除,能在定义的时候就知道表长,表长为0就是删除节束。接下来看代码:

// 带头节点的链表删除操作  
void deleteWithHead(Node** head, int position) {  
    if (*head == NULL || position < 1) {  
        printf("Invalid head or position!\n");  
        return;  
    }  
    Node* current = (*head)->next; // 跳过头节点  
    Node* prev = *head;  
    int index = 1;  
    // 查找要删除的节点的前一个节点  
    while (current != NULL && index < position) {  
        prev = current;  
        current = current->next;  
        index++;  
    }  
    // 如果current为空,说明位置超出范围  
    if (current == NULL) {  
        printf("Position out of range!\n");  
        return;  
    }  
    // 删除节点  
    prev->next = current->next;  
    free(current);  
}

// 不带头节点的链表删除操作  
void deleteWithoutHead(Node** head, int position) {  
    if (*head == NULL || position <= 0) {  
        printf("Invalid head or position!\n");  
        return;  
    }  
    Node* current = *head;  
    Node* prev = NULL;  
    int index = 1;  
    // 查找要删除的节点的前一个节点  
    while (current != NULL && index < position) {  
        prev = current;  
        current = current->next;  
        index++;  
    }  
    // 如果current为空,说明位置超出范围  
    if (current == NULL) {  
        printf("Position out of range!\n");  
        return;  
    }  
    // 删除节点  
    if (prev == NULL) {  
        // 如果要删除的是头节点  
        *head = current->next;  
    } else {  
        prev->next = current->next;  
    }  
    free(current);  
}

//顺序表删除

bool ListDelete(SqList &L)
{
    int i, e;
    printf("请输入要删除的元素位置:");
    scanf("%d",&i);
    if (i<1 || i>L.length + 1)//判断元素下标是否越界
        return false;
    if (!L.data)//判断是不是空表
    {
        printf("空表\n");
        return false;
    }
    e = L.data[i - 1];
    for (int j = i; j <= L.length; j++)
    {
        L.data[j-1] = L.data[j];
    }
    L.length--;//表长-1
    printf("删除的元素是%d,这个元素的位置是%d\n", e, i);
    return true;
}
看得懂插入,删除还会远吗,所以,就这样吧,慢慢比较吧。

二、循环单链表:

        循环链表我只在单链表上做延伸,代码我也会附上去,但是不会再比较顺序表了,只是对于带头节点的和不带头节点的进行比较

0、定义:

// 定义节点结构体  
typedef struct Node {  
    int data;  
    struct Node *next;  
} Node;  
// 定义循环单链表结构体  
typedef struct CircularLinkedList {  
    Node *head; // 指向头节点的指针  
} CircularLinkedList; 

// 定义不带头节点的循环单链表节点结构体  
typedef struct CircularLinkedListWithoutHead {  
    int data;  
    struct CircularLinkedListWithoutHead *next;  
} CircularLinkedListWithoutHead;

上面的定义自行比较...

1、初始化:

// 初始化循环单链表  
void InitCircularLinkedList(CircularLinkedList *list) {  
    list->head = (Node*)malloc(sizeof(Node));  
    if (!list->head) {  
        exit(EXIT_FAILURE);  
    }  
    list->head->next = list->head; // 初始化为循环链表  
}

// 初始化不带头节点的循环单链表  
void InitCircularLinkedListWithoutHead(CircularLinkedListWithoutHead **list) {  
    *list = NULL;  
}

上面的初始化自行比较...

2、插入:

// 插入节点到循环单链表  
void InsertNode(CircularLinkedList *list, int data) {  
    Node *newNode = (Node*)malloc(sizeof(Node));  
    if (!newNode) {  
        exit(EXIT_FAILURE);  
    }  
    newNode->data = data;  
    newNode->next = list->head->next;  
    list->head->next = newNode;  
}

// 插入节点到不带头节点的循环单链表  
void InsertNodeWithoutHead(CircularLinkedListWithoutHead **list, int data) {  
    CircularLinkedListWithoutHead *newNode = (CircularLinkedListWithoutHead*)malloc(sizeof(CircularLinkedListWithoutHead));  
    if (!newNode) {  
        exit(EXIT_FAILURE);  
    }  
    newNode->data = data;  
    if (*list == NULL) {  
        newNode->next = newNode; // 只有一个节点,指向自己  
    } else {  
        CircularLinkedListWithoutHead *current = *list;  
        while (current->next != *list) {  
            current = current->next;  
        }  
        newNode->next = *list;  
        current->next = newNode;  
    }  
    *list = newNode; // 更新头指针  
}

上面的插入自行比较...

3、删除

// 删除节点从循环单链表  
void DeleteNode(CircularLinkedList *list, int data) {  
    Node *current = list->head->next;  
    Node *prev = list->head;  
    while (current != list->head && current->data != data) {  
        prev = current;  
        current = current->next;  
    }  
    if (current == list->head) {  
        // 未找到要删除的节点  
        printf("Node with data %d not found.\n", data);  
        return;  
    }  
    prev->next = current->next;  
    free(current);  
}

// 删除节点从不带头节点循环单链表

void DeleteNodeWithoutHead(CircularLinkedListWithoutHead **list, int data) {  
    if (*list == NULL) {  
        // 链表为空  
        printf("List is empty.\n");  
        return;  
    }  
    CircularLinkedListWithoutHead *current = *list;  
    CircularLinkedListWithoutHead *prev = NULL;  
    // 查找要删除的节点  
    while (current->data != data && current->next != *list) {  
        prev = current;  
        current = current->next;  
    }  
    // 如果没有找到要删除的节点  
    if (current->data != data) {  
        printf("Node with data %d not found.\n", data);  
        return;  
    }  
    // 如果删除的是唯一节点  
    if (prev == NULL && current->next == *list) {  
        free(*list);  
        *list = NULL;  
    }   
    // 如果删除的是头节点  
    else if (prev == NULL) {  
        CircularLinkedListWithoutHead *temp = *list;  
        *list = current->next;  
        free(temp);  
    }   
    // 如果删除的是中间或尾节点  
    else {  
        prev->next = current->next;  
        free(current);  
    }  
    // 如果链表变为空,需要特殊处理  
    if (*list == NULL) {  
        return;  
    }  
    // 恢复循环链表的尾部指向头节点  
    current = *list;  
    while (current->next != *list) {  
        current = current->next;  
    }  
    current->next = *list;  
}  

上面的删除自行比较...

4、查询

// 查询节点是否在循环单链表中  
int SearchNode(CircularLinkedList *list, int data) {  
    Node *current = list->head->next;  
    while (current != list->head) {  
        if (current->data == data) {  
            return 1; // 找到节点  
        }  
        current = current->next;  
    }  
    return 0; // 未找到节点  
}

// 查询节点是否在不带头节点的循环单链表中  
int SearchNodeWithoutHead(CircularLinkedListWithoutHead *list, int data) {  
    if (list == NULL) {  
        return 0; // 链表为空  
    }  
    CircularLinkedListWithoutHead *current = list;  
    do {  
        if (current->data == data) {  
            return 1; // 找到节点  
        }  
        current = current->next;  
    } while (current != list);  
    return 0; // 未找到节点  
}

上面的查询自行比较...

        代码看完了,有没有发现一个问题,为啥不带头节点的代码相同的操作会比带头节点的代码长很多很多。当你发现这个问题的时候,就明白了,为啥人类宁愿浪费一个节点的空间也要做一个头节点出来了。因为方便省事啊...

三、循环双量表:

1、(直接gpt生成的一个带头节点的循环双链表)

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

typedef struct Node {
    int data;
    struct Node *next;
    struct Node *prev;
} Node;

typedef struct CircularDoubleLinkedList {
    Node *head;
} CircularDoubleLinkedList;

// 初始化循环双链表
void initCircularDoubleLinkedList(CircularDoubleLinkedList *list) {
    list->head = NULL;
}

// 插入节点到循环双链表
void insertNode(CircularDoubleLinkedList *list, int data) {
    Node *newNode = (Node *)malloc(sizeof(Node));
    newNode->data = data;

    if (list->head == NULL) {
        newNode->next = newNode;
        newNode->prev = newNode;
        list->head = newNode;
    } else {
        Node *last = list->head->prev;
        newNode->next = list->head;
        list->head->prev = newNode;
        newNode->prev = last;
        last->next = newNode;
    }
}

// 从循环双链表中删除节点
void deleteNode(CircularDoubleLinkedList *list, int key) {
    if (list->head == NULL)
        return;

    Node *current = list->head;
    Node *prev = NULL;

    while (current->data != key) {
        if (current->next == list->head) {
            printf("Key not found in the list.\n");
            return;
        }
        prev = current;
        current = current->next;
    }

    if (current == list->head && current->next == list->head) {
        list->head = NULL;
        free(current);
        return;
    }

    if (current == list->head) {
        prev = list->head->prev;
        list->head = list->head->next;
        prev->next = list->head;
        list->head->prev = prev;
        free(current);
    } else if (current->next == list->head) {
        prev->next = list->head;
        list->head->prev = prev;
        free(current);
    } else {
        prev->next = current->next;
        current->next->prev = prev;
        free(current);
    }
}

// 查询节点是否存在于循环双链表中
int searchNode(CircularDoubleLinkedList *list, int key) {
    if (list->head == NULL)
        return 0;

    Node *current = list->head;

    do {
        if (current->data == key)
            return 1;
        current = current->next;
    } while (current != list->head);

    return 0;
}

2、(直接gpt生成的一个不带头节点的循环双链表)

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

typedef struct Node {
    int data;
    struct Node *next;
    struct Node *prev;
} Node;

typedef struct CircularDoubleLinkedList {
    Node *head;
} CircularDoubleLinkedList;

// 初始化循环双链表
void initCircularDoubleLinkedList(CircularDoubleLinkedList *list) {
    list->head = NULL;
}

// 插入节点到循环双链表
void insertNode(CircularDoubleLinkedList *list, int data) {
    Node *newNode = (Node *)malloc(sizeof(Node));
    newNode->data = data;

    if (list->head == NULL) {
        newNode->next = newNode;
        newNode->prev = newNode;
        list->head = newNode;
    } else {
        Node *last = list->head->prev;
        newNode->next = list->head;
        list->head->prev = newNode;
        newNode->prev = last;
        last->next = newNode;
    }
}

// 从循环双链表中删除节点
void deleteNode(CircularDoubleLinkedList *list, int key) {
    if (list->head == NULL)
        return;

    Node *current = list->head;
    Node *prev = NULL;

    while (current->data != key) {
        if (current->next == list->head) {
            printf("Key not found in the list.\n");
            return;
        }
        prev = current;
        current = current->next;
    }

    if (current == list->head && current->next == list->head) {
        list->head = NULL;
        free(current);
        return;
    }

    if (current == list->head) {
        prev = list->head->prev;
        list->head = list->head->next;
        prev->next = list->head;
        list->head->prev = prev;
        free(current);
    } else if (current->next == list->head) {
        prev->next = list->head;
        list->head->prev = prev;
        free(current);
    } else {
        prev->next = current->next;
        current->next->prev = prev;
        free(current);
    }
}

// 查询节点是否存在于循环双链表中
int searchNode(CircularDoubleLinkedList *list, int key) {
    if (list->head == NULL)
        return 0;

    Node *current = list->head;

    do {
        if (current->data == key)
            return 1;
        current = current->next;
    } while (current != list->head);

    return 0;
}

因为复试在即,所以我就不自己敲代码了,过一遍代码而已,就偷懒抄别人的代码过来,或者gpt生产了一下,重在理解其中的代码逻辑和实现手法,因为代码毕竟还是自己写出来的比较顺畅,如果有喜欢的,我以后也会写一些奇奇怪怪的技术文章,其他的不重要,单纯就写写玩玩。大概率不会考虑收费,因为我学习的时候就喜欢白嫖,学到是我的本事,也是你的本事,就此这篇文章告辞,嘿嘿嘿,山高水长路还远,祝好运.....

  • 25
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值