数据结构与算法(3)-双向链表与栈与队列

1.书接上文

如何找到环的入口?

  1. 先确定环有几个节点,fast和slow相遇的那个节点,设置一个p指向此节点,count来计数

  1. 让p走,直到p再次遇到fast或者让fast走直到fast再次等于p

此时count=6,意味着环有六个节点

说明第一次循环fast指针比slow指针多走六步

  1. 重新让fast和slow指针指向头节点,fast比slow先走6步,然后同步走.下次相遇的时候指向的就是环的入口

// 找到链表环的入口节点
Node* findBegin(Node *head)
{
    Node *fast = head;
    Node *slow = head;

    // 第一步:判断是否有环
    while (fast != NULL && fast->next != NULL)
    {
        fast = fast->next->next;
        slow = slow->next;

        // 如果快慢指针相遇,说明有环
        if (fast == slow)
        {
            // 第二步:计算环的长度
            Node *p = fast;
            int count = 1;
            while (p->next != slow)
            {
                count++;
                p = p->next;
            }

            // 第三步:找环的入口
            fast = head;
            slow = head;

            // 让 fast 先走 count 步
            for (int i = 0; i < count; i++)
            {
                fast = fast->next;
            }

            // fast 和 slow 同时前进,相遇处就是环的入口
            while (fast != slow)
            {
                fast = fast->next;
                slow = slow->next;
            }

            return slow;  // 返回环入口节点
        }
    }

    return NULL; // 无环返回空
}

完整例子:

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

typedef int ElemType;

// 定义链表节点结构
typedef struct node {
    ElemType data;
    struct node *next;
} Node;

// 创建一个空链表(带头结点)
Node* initList() {
    Node *head = (Node*)malloc(sizeof(Node));
    head->next = NULL;
    return head;
}

// 尾插法建立链表
Node* insertTail(Node *head, ElemType e) {
    Node *p = (Node*)malloc(sizeof(Node));
    p->data = e;
    p->next = NULL;

    Node *tail = head;
    while (tail->next != NULL)
        tail = tail->next;

    tail->next = p;
    return head;
}

// 打印链表(最多打印20个,防止死循环)
void printList(Node *head) {
    Node *p = head->next;
    int count = 0;
    while (p != NULL && count < 20) {
        printf("%d -> ", p->data);
        p = p->next;
        count++;
    }
    printf("NULL\n");
}

// 查找环的入口
Node* findBegin(Node *head) {
    Node *fast = head;
    Node *slow = head;

    // 第一步:判断是否有环
    while (fast != NULL && fast->next != NULL) {
        fast = fast->next->next;
        slow = slow->next;

        if (fast == slow) {
            // 第二步:计算环的长度
            Node *p = fast;
            int count = 1;
            while (p->next != slow) {
                count++;
                p = p->next;
            }

            // 第三步:寻找环的入口
            fast = head;
            slow = head;
            for (int i = 0; i < count; i++) {
                fast = fast->next;
            }

            while (fast != slow) {
                fast = fast->next;
                slow = slow->next;
            }

            return slow; // 环入口
        }
    }

    return NULL; // 无环
}

int main() {
    // 创建链表
    Node *head = initList();
    for (int i = 1; i <= 6; i++) {
        insertTail(head, i);
    }

    // 手动制造一个环:让最后一个节点指向第3个节点
    Node *p = head->next;
    Node *third = NULL;
    Node *tail = NULL;

    int count = 1;
    while (p != NULL) {
        if (count == 3) third = p; // 记录第3个节点
        if (p->next == NULL) tail = p; // 记录最后一个节点
        p = p->next;
        count++;
    }
    if (tail && third)
        tail->next = third; // 形成环

    // 检测并找到环的入口
    Node *entry = findBegin(head);
    if (entry != NULL)
        printf("链表存在环,入口节点的值为:%d\n", entry->data);
    else
        printf("链表无环。\n");

    return 0;
}


2.双向链表

链式存储结构的节点中只有一个指示直接后继的指针域,由此,从某个结点出发只能顺指针向后寻查其他节点。若要寻查结点的直接前驱,则必须从表头指针出发。换句话说,在单链表中,查找直接后继的执行时间为 O(1),而查找直接前驱的执行时间为 O(n)。
为克服单链表这种单向性的缺点,可利用双向链表(Double Linked List)。在双向链表的节点中有两个指针域,一个指向直接后继,另一个指向直接前驱

typedef int ElemType;
typedef struct node
{
    ElemType data;
    struct node *prev,*next;
}Node;

  1. 头插法

// 在 双向链表 的 头结点 L 之后,插入一个新节点 p
int insertHead(Node* L,ElemType e){
    Node *p=(Node*)malloc(sizeof(Node));
    p->data=e;
    p->prev=L;
    p->next=L->next;
// 如果原本链表不为空(也就是 头节点L之后确实还有节点),
// 那么原来第一个节点(即 L->next)的 prev 指针,
// 要从原来的 L 改成现在的 p
    if (L->next!=NULL)
    {
        L->next->prev=p;
    }
    L->next=p;
    return 1;
}
  1. 尾插法

// 获取尾部节点
Node* get_tail(Node *L){
    Node *p=L;
    while (p->next!=NULL)
    {
        p=p->next;
    }
    return p;
}
// 尾插法
Node* insertTail(Node *tail,ElemType e){
    Node *p=(Node*)malloc(sizeof(Node));
    p->data=e;
    p->prev=tail;
    tail->next=p;
    p->next=NULL;
    return p;
}
  1. 在指定位置插入数据

// 指定位置插入
int insertNode(Node *L,int pos,ElemType e){
    Node *p=L;
    int i=0;
    while (i<pos-1)
    {
        p=p->next;
        i++;
        if (p==NULL)
        {
            return 0;
        }
    }
    Node *q= (Node*)malloc(sizeof(Node));
    q->data=e;
    q->prev=p;
    q->next=p->next;
    p->next->prev=q;
    p->next=q;
    return 1;
}
  1. 删除节点
  • 找到要删除节点的前置节点p
  • 用指针q记录要删除的节点
  • 通过改变p的后置节点及要删除节点的下一个节点的前驱实现删除
  • 释放删除节点的空间

int deleteNode(Node *L,int pos){
    Node *p=L;
    int i=0;
    while (i<pos-1)
    {
        p=p->next;
        i++;
        if (p==NULL)
        {
            return 0;
        }
    }
    if (p->next==NULL)
    {
        printf("要删除的位置错误\n");
        return 0;
    }
    Node *q=p->next;
    p->next=q->next;
    q->next->prev=p;
    free(q);
    return 1;
}

汇总:

// 双向链表
#include <stdio.h>
#include <stdlib.h>

typedef int ElemType;
typedef struct node
{
    ElemType data;
    struct node *prev,*next;
}Node;


// 初始化链表
Node* initList(){
    Node *head=(Node*)malloc(sizeof(Node));
    head->data=0;
    head->next=NULL;
    head->prev=NULL;
    return head;
}

// 头插法
// 在 双向链表 的 头结点 L 之后,插入一个新节点 p
int insertHead(Node* L,ElemType e){
    Node *p=(Node*)malloc(sizeof(Node));
    p->data=e;
    p->prev=L;
    p->next=L->next;
// 如果原本链表不为空(也就是 头节点L之后确实还有节点),
// 那么原来第一个节点(即 L->next)的 prev 指针,
// 要从原来的 L 改成现在的 p
    if (L->next!=NULL)
    {
        L->next->prev=p;
    }
    L->next=p;
    return 1;
}

// 遍历
void listNode(Node* L){
      Node *p=L->next;
      while (p!=NULL)
      {
        printf("%d ",p->data);
        p=p->next;
      }
      printf("\n");
}
// 获取尾部节点
Node* get_tail(Node *L){
    Node *p=L;
    while (p->next!=NULL)
    {
        p=p->next;
    }
    return p;
}
// 尾插法
Node* insertTail(Node *tail,ElemType e){
    Node *p=(Node*)malloc(sizeof(Node));
    p->data=e;
    p->prev=tail;
    tail->next=p;
    p->next=NULL;
    return p;
}
// 指定位置插入
int insertNode(Node *L,int pos,ElemType e){
    Node *p=L;
    int i=0;
    while (i<pos-1)
    {
        p=p->next;
        i++;
        if (p==NULL)
        {
            return 0;
        }
    }
    Node *q= (Node*)malloc(sizeof(Node));
    q->data=e;
    q->prev=p;
    q->next=p->next;
    p->next->prev=q;
    p->next=q;
    return 1;
}

// 删除节点
int deleteNode(Node *L,int pos){
    Node *p=L;
    int i=0;
    while (i<pos-1)
    {
        p=p->next;
        i++;
        if (p==NULL)
        {
            return 0;
        }
    }
    if (p->next==NULL)
    {
        printf("要删除的位置错误\n");
        return 0;
    }
    Node *q=p->next;
    p->next=q->next;
    q->next->prev=p;
    free(q);
    return 1;
}

// 释放链表
void freeList(Node *L){
    Node *p=L->next;
    Node *q;
    while (p!=NULL)
    {
        q=p->next;
        free(p);
        p=q;
    }
    L->next=NULL;
}

int main(){
    Node *list=initList();
    insertHead(list,10);
    insertHead(list,20);
    insertHead(list,30);
    listNode(list);

    Node *tail=get_tail(list);
    tail=insertTail(tail,10);
    tail=insertTail(tail,20);
    tail=insertTail(tail,30);
    listNode(list);

    insertNode(list,2,15);
    listNode(list);

    deleteNode(list,2);
    listNode(list);
}

至此,线性表已全部讲完

顺序表VS链表

比较项目存储结构顺序表链表
空间存储空间预先分配,会出现闲置或溢出现象动态分配,不会出现存储空间闲置或溢出现象
存储密度不用为表示节点间的逻辑关系而增加额外的存储,存储密度等于1需要借助指针来体现元素间的逻辑关系,存储密度小于1
时间存取元素随机存取,按位置访问元素的时间复杂度为 O(1)顺序存取,按位置访问元素时间复杂度为 O(n)
插入、删除平均移动约表中一半元素,时间复杂度为 O(n)不需要移动元素,确定插入、删除位置后,时间复杂度为 O(1)
适用情况1)表长变化不大,且能事先确定变化的范围; 2)很少进行插入或删除操作,经常按位置查找元素序号访问数据元素。1)长度变化较大; 2)频繁进行插入或删除操作。

做个题

  1. 第一题

链接地址就是next

答案是D

  1. 第二题

答案:D

  1. 第三题

注意!链表可能就一个头节点一个尾节点

答案:D

!!!也可能p和q是同一个节点


3.栈

栈(stack)是限定仅在表尾进行插入或删除操作的线性表。因此对栈来说,表尾端有其特殊含义,称为栈顶(top),相应地,表头端称为栈底(bottom)。不含元素的空表称为空栈

假设 S = (a₁, a₂, ⋯, aₙ),则称 a₁ 为栈底元素,aₙ 为栈顶元素。栈中元素按 a₁, a₂, ⋯, aₙ 的次序进栈,退栈的第一个元素应为栈顶元素。换句话说,栈的修改是按照后进先出的原则进行的。因此,栈又称为后进先出(Last In First Out,LIFO)的线性表

先进后出,后进先出

栈是限制插入和删除操作只能在一个位置进行的表,该位置是表的末端,叫做栈顶(top).对栈的基本操作有进栈(push)和出栈(Pop),前者表示插入,后者则是删除最后插入的元素

栈的顺序结构实现

  1. 初始化
#define MAXSIZE 100
typedef int ElemType;
typedef struct 
{
    ElemType data[MAXSIZE];
    int top;
}Stack;
void initStack(Stack *s){
    s->top=-1;
}

  1. 判断栈是否为空
int isEmpty(Stack *s){
    if (s->top==-1)
    {
        printf("空的\n");
        return 1;
    }
    else{
        return 0;
    }
}
  1. 进栈/压栈
int push(Stack *s,ElemType e){
    if (s->top>=MAXSIZE-1)
    {
        printf("满了\n");
        return 0;
    }
    s->top++;
    s->data[s->top]=e;
    return 1;
}
  1. 出栈

ElemType pop(Stack *s,ElemType *e){
    if (s->top==-1)
    {
        printf("空的\n");
        return 0;
    }
    *e=s->data[s->top];
    s->top--;
    return 1;
}
  1. 获取栈顶元素
int getTop(Stack *s,ElemType *e){
    if (s->top==-1)
    {
        printf("空的\n");
        return 0;
    }
    *e=s->data[s->top];
    return 1;
}

汇总

#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100
typedef int ElemType;
typedef struct 
{
    ElemType data[MAXSIZE];
    int top;
}Stack;
void initStack(Stack *s){
    s->top=-1;
}
// 判断栈是否为空
int isEmpty(Stack *s){
    if (s->top==-1)
    {
        printf("空的\n");
        return 1;
    }
    else{
        return 0;
    }
}
// 压栈
int push(Stack *s,ElemType e){
    if (s->top>=MAXSIZE-1)
    {
        printf("满了\n");
        return 0;
    }
    s->top++;
    s->data[s->top]=e;
    return 1;
}

// 出栈
ElemType pop(Stack *s,ElemType *e){
    if (s->top==-1)
    {
        printf("空的\n");
        return 0;
    }
    *e=s->data[s->top];
    s->top--;
    return 1;
}


// 获取栈顶元素
int getTop(Stack *s,ElemType *e){
    if (s->top==-1)
    {
        printf("空的\n");
        return 0;
    }
    *e=s->data[s->top];
    return 1;
}

int main(){
    Stack s;
    initStack(&s);
    push(&s,10);
    push(&s,20);
    push(&s,30);
    ElemType e;
    pop(&s,&e);
    printf("%d\n",e);
    getTop(&s,&e);
    printf("%d\n",e);
}
  1. 栈的顺序结构的初始化
#define MAXSIZE 100
typedef int ElemType;
typedef struct 
{
    ElemType *data;  //指针指向堆内存一片连续的空间
    int top;
}Stack;

Stack* initStack(){
    Stack *s=(Stack*)malloc(sizeof(Stack));
    s->data=(ElemType*)malloc(sizeof(ElemType)*MAXSIZE);
    s->top=-1;
    return s;
}

栈的链式实现

typedef int ElemType;
typedef struct stack
{
    ElemType data;
    struct stack *next;
}Stack;

尾节点是栈底

头节点的下一个节点就是栈顶

压栈其实就是头插法

栈空就是只有一个头节点,也就是头节点的next是否为NULL

获取栈顶数据—找头节点的下一个节点的data

  1. 初始化

Stack* initStack(){
    Stack* s=(Stack*)malloc(sizeof(Stack));
    s->data=0;
    s->next=NULL;
    return s;
}
  1. 判断栈是否为空
int isEmpty(Stack *s){
    if (s->next==NULL)
    {
        printf("空的\n");
        return 0;
    }
    else{
        return 0;
    }
}
  1. 进栈/压栈

int push(Stack *s,ElemType e){
    Stack *p=(Stack*)malloc(sizeof(Stack));
    p->data=e;
    p->next=s->next;
    s->next=p;
    return 1;
}
  1. 出栈

删除头节点后的那一个节点的数据:

  • 找到头节点p
  • 用指针q记录要出栈的节点
  • 通过改变q的后继节点实现出栈

int pop(Stack *s,ElemType *e){
    if (s->next==NULL)
    {
        printf("空的\n");
        return 0;
    }
    *e=s->next->data;
    Stack *q=s->next;
    s->next=q->next;
    free(q);
    return 1;
}
  1. 获取栈顶元素
int getTop(Stack *s,ElemType *e){
    if (s->next==NULL)
    {
        printf("空的\n");
        return 0;
    }
    *e=s->next->data;
    return 1;
}

汇总:

// 栈的链式结构实现
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct stack
{
    ElemType data;
    struct stack *next;
}Stack;

Stack* initStack(){
    Stack* s=(Stack*)malloc(sizeof(Stack));
    s->data=0;
    s->next=NULL;
    return s;
}

// 判断栈是否为空
int isEmpty(Stack *s){
    if (s->next==NULL)
    {
        printf("空的\n");
        return 0;
    }
    else{
        return 0;
    }
}

//  进栈/压栈
int push(Stack *s,ElemType e){
    Stack *p=(Stack*)malloc(sizeof(Stack));
    p->data=e;
    p->next=s->next;
    s->next=p;
    return 1;
}

// 出栈
int pop(Stack *s,ElemType *e){
    if (s->next==NULL)
    {
        printf("空的\n");
        return 0;
    }
    *e=s->next->data;
    Stack *q=s->next;
    s->next=q->next;
    free(q);
    return 1;
}

// 获取栈顶元素
int getTop(Stack *s,ElemType *e){
    if (s->next==NULL)
    {
        printf("空的\n");
        return 0;
    }
    *e=s->next->data;
    return 1;
}

int main(){
    Stack *s=initStack();
    push(s,10);
    push(s,20);
    push(s,30);
    ElemType e;
    pop(s,&e);
    printf("%d\n",e);

    getTop(s,&e);
    printf("%d\n",e);
}

4.队列

队列(queue)是一种先进先出(First In First Out,FIFO)的线性表。它只允许在表的一端进行插入,而在另一端删除元素。在队列中,允许插入的一端称为队尾(rear),允许删除的一端则称为队头(front)。假设队列为 q = (a₁, a₂, ⋯, aₙ),那么,a₁ 就是队头元素,aₙ 就是队尾元素。队列中的元素是按照 a₁, a₂, ⋯, aₙ 的顺序进入的,退出队列也只能按照这个次序依次退出,也就是说,只有在 a₁, a₂, ⋯, aₙ₋₁ 都离开队列之后,aₙ 才能退出队列。

  1. 初始化

typedef int ElemType;
typedef struct 
{
    ElemType data[MAXSIZE];
    int front;
    int rear;
}Queue;

void initQueue(Queue *Q){
    Q->front=0;
    Q->rear=0;
}
  1. 判断队列是否为空

int isEmpty(Queue *Q){
    if (Q->front==Q->rear)
    {
        printf("空的\n");
        return 1;
    }
    else{
        return 0;
    }
}
  1. 入队

int equeue(Queue *Q,ElemType e){
    if (Q->rear>=MAXSIZE)
    {
        // 发现对尾到头了
        if (!queueFull(Q))
        {
            return 0;
        }
    }
    Q->data[Q->rear]=e;
    Q->rear++;
    return 1;
}
  1. 出队

ElemType dequeue(Queue *Q){
    if (Q->front==Q->rear)
    {
        printf("空的\n");
        return 0;
    }
    ElemType e=Q->data[Q->front];
    Q->front++;
    return e;
}
  1. 对尾满了,调整队列
int queueFull(Queue *Q){
    if (Q->front>0)
    {
        int step=Q->front;
        for (int i = Q->front; i <= Q->rear; ++i)
        {
            Q->data[i-step]=Q->data[i];
        }
        Q->front=0;
        Q->rear=Q->rear-step;
        return 1;
    }
    else{
        printf("真的满了\n");
        return 0;
    }
}

  1. 获取对头元素
int getHead(Queue *Q,ElemType *e){
    if (Q->front==Q->rear)
    {
        printf("空的\n");
        return 0;
    }
    *e=Q->data[Q->front];
    return 1;
}

汇总:

#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100
typedef int ElemType;
typedef struct 
{
    ElemType data[MAXSIZE];
    int front;
    int rear;
}Queue;

void initQueue(Queue *Q){
    Q->front=0;
    Q->rear=0;
}
// 预声明,避免隐式声明告警
int queueFull(Queue *Q);


// 判断队列是否为空
int isEmpty(Queue *Q){
    if (Q->front==Q->rear)
    {
        printf("空的\n");
        return 1;
    }
    else{
        return 0;
    }
}


// 出队
ElemType dequeue(Queue *Q){
    if (Q->front==Q->rear)
    {
        printf("空的\n");
        return 0;
    }
    ElemType e=Q->data[Q->front];
    Q->front++;
    return e;
}

// 入队
int equeue(Queue *Q,ElemType e){
    if (Q->rear>=MAXSIZE)
    {
        if (!queueFull(Q))
        {
            return 0;
        }
    }
    Q->data[Q->rear]=e;
    Q->rear++;
    return 1;
}

// 队尾满了,调整队列
int queueFull(Queue *Q){
    if (Q->front>0)
    {
        int step=Q->front;
        for (int i = Q->front; i <= Q->rear; ++i)
        {
            Q->data[i-step]=Q->data[i];
        }
        Q->front=0;
        Q->rear=Q->rear-step;
        return 1;
    }
    else{
        printf("真的满了\n");
        return 0;
    }
}

// 获取队头元素
int getHead(Queue *Q,ElemType *e){
    if (Q->front==Q->rear)
    {
        printf("空的\n");
        return 0;
    }
    *e=Q->data[Q->front];
    return 1;
}

int main(){
    Queue q;
    initQueue(&q);

    equeue(&q,10);
    equeue(&q,20);
    equeue(&q,30);
    equeue(&q,40);
    equeue(&q,50);
    printf("%d\n",dequeue(&q));
    printf("%d\n",dequeue(&q));
    ElemType e;
    getHead(&q,&e);
    printf("%d\n",e);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值