数据结构——顺序表与链表

news2025/3/12 19:19:04

目录

前言

一线性表

二顺序表

1实现

2相关面试题

2.1移除元素

2.2删除有序数组中的重复项 

3.3合并两个有序数组

3问题

三链表

1链表的分类

1.1单向或者双向

1.2带头或者不带头

1.3循环或者非循环

2实现

2.1尾插与头插

2.2尾删与头删

2.3pos前插入节点与删除pos位置

四相关练习题 

链表的中间结点

移除链表元素

反转链表

链表分割

合并两个有序链表

链表中倒数第k个结点

相交链表

环形链表

环形链表 II

链表的回文结构

随机链表的复制

五双链表

实现

六总结

七其它知识


前言

学习顺序表和链表一定要画图!!!图画的好学起来就简单些!!!

一线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。                                                线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...

线性表在逻辑上是线性结构,也就说是连续的一条直线。                                                              但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式的形式储存

二顺序表

顺序表用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般采用数组存储

一般分为两种:

静态储存(不常用)

动态储存(实践常用)

1实现

实现顺序表一般是实现动态顺序表的增删查改

//SqList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLDataType;

typedef struct SqList
{
    SLDataType*_a;
    int _size;
    int _capacity;
}SL;

void SLInit(SL* ps);
void SLDestory(SL* ps);

void SLPushBack(SL* ps,SLDataType x);
void SLPushFront(SL*ps,SLDataType x);

void SLPopBack(SL* ps);
void SLPopFront(SL* ps);

void SLPrint(SL* ps);

void SLInsert(SL* ps,int pos,SLDataType x);
void SLErase(SL* ps,int pos);

int SLFindPos(SL* ps,SLDataType x);

//SqList.c
#include "SqList.h"

// 实现时进行的ps都要进行检查,防止为空出现越界访问
void SLInit(SL *ps)
{
    assert(ps);
    // 这里可malloc也可不malloc
    ps->_a = NULL;
    ps->_capacity = 0;
    ps->_size = 0;
}
void SLDestory(SL *ps)
{
    assert(ps);
    free(ps->_a);
    ps->_a = NULL;
    ps->_capacity = 0;
    ps->_size = 0;
}

static void SLCheckCapacity(SL *ps)
{
    if (ps->_capacity == ps->_size)
    {
        int newcapacity = ps->_capacity == 0 ? 2 : ps->_capacity * 2;
        SLDataType *new = (SLDataType *)realloc(ps->_a, newcapacity); // ps->a为空是相当于malloc
        if (new == NULL)
        {
            perror("realloc error");
            exit(-1);
        }
        ps->_a = new;
        ps->_capacity = newcapacity;
    }
}

void SLPushBack(SL *ps, SLDataType x)
{
    assert(ps);
    SLCheckCapacity(ps);
    ps->_a[ps->_size] = x;
    ps->_size++;
}
void SLPushFront(SL *ps, SLDataType x)
{
    assert(ps);
    SLCheckCapacity(ps);
    for (int end = ps->_size; end > 0; end--)
    {
        ps->_a[end] = ps->_a[end - 1];
    }
    ps->_a[0] = x;
    ps->_size++;
}

void SLPopBack(SL *ps)
{
    assert(ps);

    // ps->_size为0进行删除不符合规定(没数据怎么删)
    assert(ps->_size > 0);
    // ps->_a[ps->_size-1]=-1;//1删除数据让新数据进行覆盖即可;2如果该位置的值刚好是-1(不是int类型)呢?
    ps->_size--;
}
void SLPopFront(SL *ps)
{
    assert(ps);

    assert(ps->_size > 0);
    for (int begin = 0; begin < ps->_size - 1; begin++)
    {
        ps->_a[begin] = ps->_a[begin + 1];
    }
    ps->_size--;
}

void SLPrint(SL *ps)
{
    for (int i = 0; i < ps->_size; i++)
    {
        printf("%d ", ps->_a[i]);
    }
    printf("\n");
}

void SLInsert(SL *ps, int pos, SLDataType x)
{
    assert(ps);
    SLCheckCapacity(ps);

    assert(pos >= 0 && pos <= ps->_size);

    for (int end = ps->_size; end > pos; end--)
    {
        ps->_a[end] = ps->_a[end - 1];
    }
    ps->_a[pos] = x;
    ps->_size++;
}
void SLErase(SL *ps, int pos)
{
    assert(ps);

    assert(pos >= 0 && pos < ps->_size);

    for (int begin = pos; begin < ps->_size - 1; begin++)
    {
        ps->_a[begin] = ps->_a[begin + 1];
    }
    ps->_size--;
}

int SLFindPos(SL *ps, SLDataType x)
{
    assert(ps);
    for (int i = 0; i < ps->_size; i++)
    {
        if (ps->_a[i] == x)
            return i;
    }
    return -1;
}

//Test.c
// #include"SqList.h"//实现函数SqList.c先进行汇编后生成的文件与Test.c一起编译
#include "SqList.c"

void Test1()
{
    SL s;
    SLInit(&s);
    SLPushBack(&s, 1);
    SLPushBack(&s, 2);
    SLPushBack(&s, 3);
    SLPrint(&s);
    SLPushFront(&s, 5);
    SLPrint(&s);
    SLDestory(&s);
}

void Test2()
{

    SL s;
    SLInit(&s);
    SLPushBack(&s, 1);
    SLPushBack(&s, 2);
    SLPushBack(&s, 3);
    SLPushBack(&s, 4);
    SLPushBack(&s, 5);
    SLPrint(&s);
    SLPopBack(&s);
    SLPopBack(&s);
    SLPopFront(&s);
    SLPrint(&s);
    SLDestory(&s);
}

void Test3()
{
    SL s;
    SLInit(&s);
    SLInsert(&s, 0, 10);
    SLPrint(&s);

    SLErase(&s, 0);
    SLPrint(&s);

    SLDestory(&s);
}

void Test4()
{
    SL s;
    SLInit(&s);
    SLPushBack(&s, 1);
    SLPushBack(&s, 2);
    SLPushBack(&s, 3);
    SLPrint(&s);

    int pos = SLFindPos(&s, 2);
    if (pos != -1)
    {
        SLErase(&s, pos);
    }
    SLPrint(&s);
}

int main()
{
    // Test1();
    // Test2();
    // Test3();
    Test4();
    return 0;
}

2相关面试题

2.1移除元素

思路1 O(N) = N O(n) = n

定义一个辅助数组,把不是val的值尾插到该数组中,最后再拷贝到原数组中;

思路2 O(N) = N O(n) = 1

定义两个指针src,des;

int removeElement(int *nums, int numsSize, int val)
{
    int src = 0, des = 0;
    while (des < numsSize)
    {
        if (nums[des] != val)
            nums[src++] = nums[des++];
        else
            des++;
    }
    return src;
}

2.2删除有序数组中的重复项 

思路:

定义两个指针src=0,des=1;

当nums[src]==nums[des]时只需des++即可

如果不相等,这时 nums[++src] = nums[des++] (注意是先++src再赋值) 

本质就是利用两个指针进行去重操作

int removeDuplicates(int *nums, int numsSize)
{
    int src = 0, des = 1;
    while (des < numsSize)
    {
        if (nums[src] == nums[des])
            des++;
        else
            nums[++src] = nums[des++];
    }
    return src + 1;
}

3.3合并两个有序数组

思路1 O(N) = N O(n) = N

开辟一个辅助数组,定义两个指针分别指向两个数组起始位置,取小的进行尾插到辅助数组后指向小位置的指针向后移动

思路2 O(N) = N O(n) = 1

题目要我们把结果放到nums1中且已经开好足够的空间;

如果按照思路1的方法取下的尾插到nums1中势必会造成数组丢失(nums1中元素被覆盖)

所以我们定义两个指针i1,i2分别指向两个数组末尾(num1的末尾是等于m),再定义指针i指向num1的真正末尾处,取大的数放到该位置后i--,指向大的数的位置的指针(例如是i1)--

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
	int i1 = m - 1, i2 = n - 1, i = nums1Size - 1;
	//关键在移动比较的是i1和i2,哪个停下来就说明哪个走完了
	while (i1 >= 0 && i2 >= 0)
	{
		if (nums1[i1] > nums2[i2]) nums1[i--] = nums1[i1--];
		else nums1[i--] = nums2[i2--];
	}
	//i2没走完要进行拷贝过去
	while (i2 >= 0) nums1[i--] = nums2[i2--];
}

3问题

1.尾插效率还行;

   但如果是头插或者在任意位置进行插入或者删除时就需要移动数据,效率差;

2.进行扩容时可能导致频繁扩容或者扩容出来的空间可能被浪费

(比如扩容100个只存了1个) 

解决上面的问题,用一种新的数据结构——链表来解决!

三链表

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的

有点像火车的形状

1链表的分类

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构;

1.1单向或者双向

1.2带头或者不带头

1.3循环或者非循环

但最常用的只有:

2实现

首先在传参数上:传的是指针还是指针的地址

头节点head变量是指针变量,进行尾插时与顺序表一样:也是要传地址过去(因为需要涉及链表的修改,把尾节点的下一个链接上一个新节点);如果不传地址,直接传指针的话会找不到新节点(形参的改变不影响实参(指针也是一样!))

2.1尾插与头插

void SLTPushBack(SLNode **phead, SLTDataType x)
{
    SLNode *node = CreateNode(x);
    if (*phead == NULL)
    {
        *phead = node;
    }
    else
    {
        SLNode *tail = *phead;

        while (tail->_next != NULL)
        {
            tail = tail->_next;
        }
        tail->_next = node;

        // 问题1.tail->next是链接不到node的;2.tail是局部变量,这样写会引发内存泄漏
        //  while(tail!=NULL)
        //  {
        //      tail=tail->_next;
        //  }
        //  tail=node;
    }
}

void SLTPushFront(SLNode **pphead, SLTDataType x)
{
    SLNode *node = CreateNode(x);
    node->_next = *pphead;
    *pphead = node;
}

2.2尾删与头删

void SLTPopBack(SLNode **pphead)
{
    assert(*pphead);
    // 1.一个节点
    // 2.多个节点
    if ((*pphead)->_next == NULL)
    {
        free(*pphead);
        *pphead = NULL;
    }
    else
    {
        // 找尾
        SLNode *prev = NULL;
        SLNode *tail = *pphead;
        while (tail->_next != NULL)
        {
            prev = tail;
            tail = tail->_next;
        }

        free(tail);
        prev->_next = NULL;
        tail == NULL;
    }
}

那如果是这样写呢?

void SLTPopBack(SLNode **pphead)
{
        // 找尾
        SLNode *prev = *pphead;//这里修改
        SLNode *tail = *pphead;
        while (tail->_next != NULL)
        {
            prev = tail;
            tail = tail->_next;
        }

        free(tail);
        prev->_next = NULL;
        tail == NULL;
}

void SLTPopFront(SLNode **pphead)
{
    assert(*pphead);
    //一个节点/多个节点
    SLNode *tmp = (*pphead)->_next;
    free(*pphead);
    *pphead = tmp;
}

 

2.3pos前插入节点与删除pos位置

void SLTErase(SLNode **pphead, SLNode *pos)
{
    assert(pphead);
    assert(*pphead);
    assert(pos);

    if(*pphead==pos)
    {
        //头删
        SLTPopFront(pphead);
    }
    else
    {
        SLNode *prev = *pphead;
        while (prev->_next!= pos)
        {
            prev = prev->_next;
        }
        prev->_next=pos->_next;
        free(pos);
        pos=NULL;
    }
}

void SLTErase(SLNode **pphead, SLNode *pos)
{
    assert(pphead);
    assert(*pphead);
    assert(pos);

    if(*pphead==pos)
    {
        //头删
        SLTPopFront(pphead);
    }
    else
    {
        SLNode *prev = *pphead;
        while (prev->_next!= pos)
        {
            prev = prev->_next;
        }
        prev->_next=pos->_next;
        free(pos);
        pos=NULL;
    }
}

还有两个相似的接口:在pos后插入节点和删除pos后的节点:没有头插与头删的情况比较简单

void SLTInsertAfter(SLNode **pphead, SLNode *pos, SLTDataType x)
{
    assert(pphead);
    assert(*pphead);
    assert(pos);

    SLNode* newnode=CreateNode(x);
    newnode->_next=pos->_next;
    pos->_next=newnode;

}

void SLTEraseAfter(SLNode **pphead, SLNode *pos)
{
    assert(pphead);
    assert(*pphead);
    assert(pos);

    SLNode* next=pos->_next;
    pos->_next=pos->_next->_next;
    free(next);
    next=NULL;
}

四相关练习题 

链表的中间结点

struct ListNode *middleNode(struct ListNode *head)
{
    struct ListNode *slow = head;
    struct ListNode *fast = head;
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

移除链表元素

思路一:

套用实现删除pos位置接口的思路,要分为两种情况:删除节点是不是头节点

struct ListNode *removeElements(struct ListNode *head, int val)
{
    struct ListNode *cur = head;
    struct ListNode *prev = NULL;
    while (cur != NULL)
    {
        if (cur->val == val)
        {
            struct ListNode *next = cur->next;
            if (cur == head)
                head = next; // 头删
            free(cur);
            cur = next;

            if (prev)
                prev->next = next; // 不是头删的情况
        }
        else
        {
            prev = cur;
            cur = cur->next;
        }
    }
    return head;
}

思路2:

不是删除的节点链接到新链表中,返回新链表的头节点

struct ListNode *removeElements(struct ListNode *head, int val)
{
    if (head == NULL)
        return NULL;
    struct ListNode *ret = (struct ListNode *)malloc(sizeof(struct ListNode));
    struct ListNode *cur = head;
    struct ListNode *tail = ret;
    while (cur)
    {
        if (cur->val != val)
        {
            tail->next = cur;

            tail = tail->next;
            cur = cur->next;
        }
        else
        {
            struct ListNode *next = cur->next;
            free(cur);
            cur = next;
        }
    }
    tail->next = NULL;
    struct ListNode *ans = ret->next;
    free(ret);
    ret = NULL;
    return ans;
}

反转链表

思路1:创建新链表进行一次头插操作

 思路2:定义三个指针prev,cur,next:修改链表箭头指向

 

struct ListNode* reverseList(struct ListNode* head) 
{
    struct ListNode* ret = NULL;
    while (head)
    {
        struct ListNode *next = head->next;
        // 头插
        head->next = ret;
        ret = head;

        head = next;
    }
    return ret;
}

struct ListNode* reverseList(struct ListNode* head) 
{
    if (head == NULL)
        return NULL;
    struct ListNode *prev = NULL, *cur = head, *next = cur->next;
    while (cur)
    {
        cur->next = prev;

        prev = cur;
        cur = next;
        if (next)
            next = next->next;
    }
    return prev;
}

链表分割

思路:定义两个链表:一个储存小于x的节点,一个储存大于x的节点;最后合并两个链表(建议定义带哨兵位链表,不用额外去判断特殊情况(其中一个链表可能为空)) 

class Partition
{
public:
    ListNode *partition(ListNode *pHead, int x)
    {
        ListNode *small = NULL, *tail1 = NULL;
        ListNode *big = NULL, *tail2 = NULL;
        while (pHead)
        {
            if (pHead->val < x)
            {
                if (small == NULL)
                    small = tail1 = pHead;
                else
                {
                    tail1->next = pHead;
                    tail1 = tail1->next;
                }
            }
            else
            {
                if (big == NULL)
                    big = tail2 = pHead;
                else
                {
                    tail2->next = pHead;
                    tail2 = tail2->next;
                }
            }
            pHead = pHead->next;
        }
        if (tail1 == NULL)
            return big;
        if (tail2 == NULL)
            return small;

        tail1->next = big;
        tail2->next = NULL;
        return small;
    }
};

合并两个有序链表

思路:两个链表从头开始比较,取小的尾插到新链表后往后走 

struct ListNode *mergeTwoLists(struct ListNode *list1, struct ListNode *list2)
{
    if (list1 == NULL)
        return list2;
    if (list2 == NULL)
        return list1;
    struct ListNode *list = NULL, *tail = NULL;
    while (list1 && list2)
    {
        if (list1->val < list2->val)
        {
            if (list == NULL)
                list = tail = list1;
            else
            {
                tail->next = list1;
                tail = list1;
            }
            list1 = list1->next;
        }
        else
        {
            if (list == NULL)
                list = tail = list2;
            else
            {
                tail->next = list2;
                tail = list2;
            }
            list2 = list2->next;
        }
    }
    if (list1)
        tail->next = list1;
    if (list2)
        tail->next = list2;
    return list;
}

链表中倒数第k个结点

思路1:模拟

先遍历出有多少个节点cnt,再用cnt - k 得出需要走节点到达倒数第k个节点

(cnt - k <0 则直接返回NULL) 

struct ListNode *FindKthToTail(struct ListNode *pListHead, int k)
{
    struct ListNode *cur = pListHead;
    int cnt = 0;
    while (cur)
    {
        cnt++;
        cur = cur->next;
    }
    cnt = cnt - k;
    if (cnt < 0)
        return NULL;
    while (cnt--)
    {
        pListHead = pListHead->next;
    }
    return pListHead;
}

思路2:快慢双指针 

struct ListNode *FindKthToTail(struct ListNode *pListHead, int k)
{
    struct ListNode *slow = pListHead, *fast = pListHead;
    while (k--)
    {
        if (fast == NULL)
            return NULL;
        fast = fast->next;
    }
    while (fast)
    {
        slow = slow->next;
        fast = fast->next;
    }
    return slow;
}

相交链表

思路1

两层for循环来判断AB链表是否有节点是相等的

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
    while (headA)
    {
        struct ListNode *head = headB;
        while (head)
        {
            if (headA == head)
                return head;
            head = head->next;
        }
        headA = headA->next;
    }
    return NULL;
}

思路2:双指针 

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
    struct ListNode *curA = headA, *curB = headB;
    int lenA = 1, lenB = 1;
    while (curA->next)
    {
        lenA++;
        curA = curA->next;
    }
    while (curB->next)
    {
        lenB++;
        curB = curB->next;
    }
    if (curA != curB)
        return NULL; // 尾节点不同就一定不相交

    struct ListNode *longNode = headA, *shortNode = headB;
    if (lenA < lenB)
    {
        longNode = headB;
        shortNode = headA;
    }
    int tmp = abs(lenA - lenB);
    // 长的先走tmp步
    while (tmp--)
    {
        longNode = longNode->next;
    }
    while (longNode != shortNode)
    {
        longNode = longNode->next;
        shortNode = shortNode->next;
    }
    return longNode;
}

环形链表

思路1:利用set容器储存节点地址,判断next是否在set容器中

class Solution
{
public:
    bool hasCycle(ListNode *head)
    {
        unordered_set<ListNode *> hash;
        while (head)
        {
            if (hash.count(head->next))
                return true;
            hash.insert(head);
            head = head->next;
        }
        return false;
    }
};

思路2:快慢双指针 

定义两个指针slow fast:slow走一步,fast走两步;如果相等就有环 

bool hasCycle(struct ListNode *head)
{
    struct ListNode *slow = head, *fast = head;
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast)
            return true;
    }
    return false;
}

总结:1如果N是偶数就追上了;

           2如果N是奇数,C是奇数,要第二圈才能追上;

           3如果N是奇数,C是偶数,就永远追不上;

           第3个结论是否成立呢?

把X换成N的等式:

环形链表 II

struct ListNode *detectCycle(struct ListNode *head)
{
    struct ListNode *slow = head, *fast = head;
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast)
        {
            slow = head;
            while (slow != fast)
            {
                slow = slow->next;
                fast = fast->next;
            }
            return slow;
        }
    }
    return NULL;
}

链表的回文结构

思路:找到链表中间节点,将中间节点后半段的链表进行逆置后进行比较val

ListNode *GainMiddle(ListNode *A)
{
    ListNode *slow = A, *fast = A;
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

ListNode *Reverse(ListNode *A)
{
    ListNode *head = NULL;
    while (A)
    {
        ListNode *next = A->next;
        A->next = head;
        head = A;
        A = next;
    }
    return head;
}

class PalindromeList
{
public:
    bool chkPalindrome(ListNode *A)
    {
        ListNode *mid = GainMiddle(A);
        ListNode *rhead = Reverse(mid);
        while (rhead && A)
        {
            if (rhead->val != A->val)
                return false;
            rhead = rhead->next;
            A = A->next;
        }
        return true;
    }
};

随机链表的复制

思路1(O(N) = N^2)

a先拷贝一个新链表;

b遍历一遍,找到原链表的节点->random指向节点的相对位置cnt

c通过cnt再次遍历一遍,找到新链表的节点->random指向的节点

struct Node *copyRandomList(struct Node *head)
{
    if (head == NULL)
        return NULL;
    struct Node *head1 = NULL, *tail = NULL;
    struct Node *cur = head;
    while (cur)
    {
        // 拷贝节点到新链表中
        struct Node *copy = (struct Node *)malloc(sizeof(struct Node));
        copy->val = cur->val;
        if (head1 == NULL)
            head1 = tail = copy;
        else
        {
            tail->next = copy;
            tail = tail->next;
        }
        cur = cur->next;
    }
    tail->next = NULL; // 新链表拷贝完成后结尾置NULL
    cur = head;
    struct Node *cur1 = head1;
    while (cur)
    {
        if (cur->random == NULL)
            cur1->random = NULL;
        else
        {
            int cnt = 0;
            struct Node *tmp = head;
            while (cur->random != tmp)
            {
                cnt++;
                tmp = tmp->next;
            }
            // 此时cnt是cur->random的相对位置,新链表对应的节点根据cnt找到random
            tmp = head1;
            while (cnt)
            {
                tmp = tmp->next;
                cnt--;
            }
            cur1->random = tmp;
        }
        cur = cur->next;
        cur1 = cur1->next;
    }
    return head1;
}

思路2(O(N) = N)

a拷贝节点放到原节点后面

b拷贝节点->random = 原节点->random->next

c最后把拷贝节点连到新链表中

struct Node *copyRandomList(struct Node *head)
{
    if (head == NULL)
        return NULL;
    struct Node *cur = head;
    while (cur)
    {
        struct Node *copy = (struct Node *)malloc(sizeof(struct Node));
        copy->val = cur->val;
        // 插入在原节点的后面
        copy->next = cur->next;
        cur->next = copy;

        cur = copy->next;
    }
    cur = head;
    while (cur)
    {
        // 完成节点随机指向
        struct Node *copy = cur->next;
        if (cur->random == NULL)
        {
            copy->random = NULL;
        }
        else
            copy->random = cur->random->next;

        cur = copy->next;
    }
    struct Node *ret = NULL, *tail = NULL;
    cur = head;
    while (cur)
    {
        // 获取新链表
        struct Node *copy = cur->next;
        struct Node *next = copy->next;

        if (ret == NULL)
            ret = tail = copy;
        else
        {
            tail->next = copy;
            tail = tail->next;
        }

        cur->next = next;
        cur = next;
    }
    return ret;
}

五双链表

以上我们实现或者是在做题时用到的都是单链表(无哨兵位):结构简单,但一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等;

双链表(带头):实践用的最多,一般用在单独存储数据;虽然结构较复杂,但实现出来后会发现它比单链表具有很多优势(如插入删除方便),实用性也比较强

实现

//DList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int DListDateType;

typedef struct DListNode
{
    DListDateType _val;
    struct DListNode *_next;
    struct DListNode *_prev;
} DList;

DList *CreateNode(DListDateType val);
DList *Init();
void Print(DList *head);
void DLPushBack(DList *head, DListDateType val);
void DLPushFront(DList *head, DListDateType val);
void DLPopBack(DList *head);
void DLPopFront(DList *head);

DList *DLFind(DList *head, DListDateType val);
void DLInsert(DList *pos, DListDateType val);
void DLErase(DList *pos);

void Destoty(DList *head);
void DLPushBack(DList *head, DListDateType val)
{
    assert(head);
    DList *NewNode = CreateNode(val);

    DList *tail = head->_prev;
    tail->_next = NewNode;
    NewNode->_prev = tail;
    NewNode->_next = head;
    head->_prev = NewNode;
}

void Print(DList *head)
{
    assert(head);
    DList *cur = head->_next;
    printf("哨兵位<=>");
    while (cur != head)
    {
        printf("%d<=>", cur->_val);
        cur = cur->_next;
    }
    printf("哨兵位\n");
}

void DLPopBack(DList *head)
{
    assert(head);
    assert(head->_next != head); // 至少有节点删除

    DList *tail = head->_prev;
    DList *prev = tail->_prev;

    prev->_next = head;
    head->_prev = prev;
    free(tail);
    tail = NULL;
}

void DLPushFront(DList *head, DListDateType val)
{
    assert(head);
    DList *NewNode = CreateNode(val);
    DList *first = head->_next;

    NewNode->_next = first;
    first->_prev = NewNode;
    head->_next = NewNode;
    NewNode->_prev = head;
}

void DLPopFront(DList *head)
{
    assert(head);
    assert(head->_next != head);

    DList *first = head->_next;
    DList *second = first->_next;

    head->_next = second;
    second->_prev = head;

    free(first);
    first = NULL;
}

void DLInsert(DList *pos, DListDateType val)
{
    assert(pos);

    DList *node = CreateNode(val);
    DList *prev = pos->_prev;

    prev->_next = node;
    node->_prev = prev;
    node->_next = pos;
    pos->_prev = node;
}

void DLErase(DList *pos)
{
    assert(pos);

    DList *next = pos->_next;
    DList *prev = pos->_prev;

    prev->_next = next;
    next->_prev = prev;

    free(pos);
    // pos=NULL;形参的改变不影响实参
}

其它接口

void Destory(DList *head)
{
    assert(head);

    DList *cur = head->_next;
    while (cur != head)
    {
        DList *next = cur->_next;
        free(cur);
        cur = next;
    }
    free(cur);
    // cur=NULL;
}

DList *DLFind(DList *head, DListDateType val)
{
    assert(head);

    DList *cur = head->_next;
    while (cur != head)
    {
        if (cur->_val == val)
            return cur;
        cur = cur->_next;
    }
    return NULL;
}

六总结

顺序表

问题:a.头部插入或者删除效率低:O(N) = N,需要挪动数据;

           b.空间不够需要扩容,扩容有一定的消耗且有可能存在一定的浪费;

           c.只适合尾插尾删

优点:支持下标随机访问

链表(双向)

问题:下标随机访问不方便:O(N) = N;

优点:a.插入与删除效率高:O(N) = 1,只需要改变指针指向;

           b.按需申请释放,合理利用空间,不存在浪费

七其它知识

在学习计算机的基础知识时,我们知道:计算机进行运算靠的是CPU,而存储靠的是内存和磁盘,那为什么储存会有两个呢?

内存是带电储存,读写速度更快;而磁盘是不带电储存,读写速度较慢;这也就决定了两者根据场景来进行需要用那个来储存数据

CPU运行速度快,这也取决于在它附件的三个高速缓存和寄存器 先把数据加载进来:

CPU对数据的访问时,首先要从缓存里看下在不在?在的话就是命中,不在的话就是不命中;

由于顺序表储存是在一大段连续的空间中,CPU访问时,如果不在,它会一次加载一段数据到缓存中,那么接下来访问下一个时就是在缓存区中了,直接进行访问;   

而链表逻辑上连续,而物理上是不连续的,CPU访问时某个节点时加载一段数据到缓存,后面的节点是大概率没被加载到缓存中的,所以下次访问下个节点时有需要进行加载,而缓存空间不大,这就造成缓存污染问题

以上便是全部内容,有问题欢迎在评论区指正,感谢观看! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2299729.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在 Python 中使用 Ollama API

文章目录 一、环境准备二、使用方法1.简单对话2.流式响应3.结构化输出4.自定义客户端4.1 同步客户端4.2 异步客户端4.3 同步 & 异步客户端不同调用次数耗时对比测试 三、常用的ollama API 接口聊天生成本地模型列表显示模型信息创建模型复制模型删除模型拉取模型推送模型生…

BGP配置华为——RR反射器配置

实验拓扑 与之前实验同理将loop0作为routerID使用&#xff0c;且R1和R2上用loop1接口用于模拟用户其他网段 实验要求 1&#xff0c;在AS100内运行OSPF协议 2.配置路由反射器&#xff0c;使得从R1进入的数据能够反射到全局网络 3.在R1和R2上分别宣告自己的loop1口网段用于观…

一.AI大模型开发-初识机器学习

机器学习基本概念 前言 本文主要介绍了深度学习基础&#xff0c;包括机器学习、深度学习的概念&#xff0c;机器学习的两种典型任务分类任务和回归任务&#xff0c;机器学习中的基础名词解释以及模型训练的基本流程等。 一.认识机器学习 1.人工智能和机器学习 人工智能&am…

力扣做题记录 (二叉树)

二叉树 打算先来了解二叉树基础&#xff0c;都是简单题&#xff0c;目的是熟悉代码格式和解题基础思路。 1、二叉树最大深度 二叉树最大深度 方法一、深度搜索 直接用原函数做递归&#xff0c;比较简单 /*** Definition for a binary tree node.* struct TreeNode {* …

前端里的this指向问题

目录 1.代码输出结果 2.代码输出结果 3.代码输出结果 4.代码输出结果 5.代码输出结果 6.代码输出结果 7.代码输出结果 8.代码输出结果 9.代码输出结果 10.代码输出结果 11.代码输出结果 12.代码输出结果 13.代码输出结果 14.代码输出结果 总结 1.代码输出结果 f…

黑马Redis详细笔记(实战篇---短信登录)

目录 一.短信登录 1.1 导入项目 1.2 Session 实现短信登录 1.3 集群的 Session 共享问题 1.4 基于 Redis 实现共享 Session 登录 一.短信登录 1.1 导入项目 数据库准备 -- 创建用户表 CREATE TABLE user (id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 用户ID,phone …

基于N-gram模型的中文文本分析系统设计与实现

前言 在数字化人文研究快速发展的背景下&#xff0c;中文古典文本的量化分析面临着独特的挑战。古典文献中繁简异体字共存、语义单元边界模糊、意象隐喻密集等特征&#xff0c;使得传统的词频统计方法难以准确捕捉其深层语言规律。现有文本分析工具多面向现代汉语设计&#xff…

零基础购买阿里云服务器,XShell连接云服务器

目录 1.环境搭建方式 2. 使用云服务器 3.使用终端软件登录到Linux 4.使用XShell登录主机 5.连接失败的原因&#xff1a; 下一篇更新&#xff1a;Linux的基础指令以及如何Linux的环境搭建 1.环境搭建方式 主要有四种: 1.直接安装在物理机上&#xff0c;虽然Linux有图形化…

CNN手写数字识别1——模型搭建与数据准备

模型搭建 我们这次使用LeNet模型&#xff0c;LeNet是一个经典的卷积神经网络&#xff08;Convolutional Neural Network, CNN&#xff09;架构&#xff0c;最初由Yann LeCun等人在1998年提出&#xff0c;用于手写数字识别任务 创建一个文件model.py。实现以下代码。 源码 #…

深度学习04 数据增强、调整学习率

目录 数据增强 常用的数据增强方法 调整学习率 学习率 调整学习率 ​调整学习率的方法 有序调整 等间隔调整 多间隔调整 指数衰减 余弦退火 ​自适应调整 自定义调整 数据增强 数据增强是通过对训练数据进行各种变换&#xff08;如旋转、翻转、裁剪等&#xff09;&am…

PH热榜 | 2025-02-16

1. Cal.com Routing 标语&#xff1a;根据客户线索&#xff0c;系统会智能地自动安排约会。 介绍&#xff1a;告别繁琐的排期&#xff01;Cal.com 推出了新的路由功能&#xff0c;能更智能地分配预约&#xff0c;让你的日程安排更顺畅。这项功能运用智能逻辑和深入的数据分析…

数据库基本概念及基本使用

数据库基本概念 什么是数据库&#xff1a; 数据库特点&#xff1a; 常见的数据库软件&#xff1a; 不同的公司进行不同的实践&#xff0c;生成了不同的产品。 比如买汽车&#xff0c;汽车只是一个概念&#xff0c;你要买哪个牌子哪个型号的汽车&#xff0c;才是真正的汽车的一…

gozero实现数据库MySQL单例模式连接

在 GoZero 框架中实现数据库的单例连接可以通过以下步骤来完成。GoZero 使用 gorm 作为默认的数据库操作框架&#xff0c;接下来我会展示一个简单的单例模式实现。 ### 1. 定义数据库连接的单例结构 首先&#xff0c;你需要定义一个数据库连接的结构体&#xff0c;并在初始化…

CSS flex布局 列表单个元素点击 本行下插入详情独占一行

技术栈&#xff1a;Vue2 javaScript 简介 在实际开发过程中有遇到一个场景&#xff1a;一个list&#xff0c;每行个数固定&#xff0c;点击单个元素后&#xff0c;在当前行与下一行之间插入一行元素详情&#xff0c;便于更直观的查看到对应的数据详情。 这种情形&#xff0c…

无人机航迹规划: 梦境优化算法(Dream Optimization Algorithm,DOA)求解无人机路径规划MATLAB

一、梦境优化算法 梦境优化算法&#xff08;Dream Optimization Algorithm&#xff0c;DOA&#xff09;是一种新型的元启发式算法&#xff0c;其灵感来源于人类的梦境行为。该算法结合了基础记忆策略、遗忘和补充策略以及梦境共享策略&#xff0c;通过模拟人类梦境中的部分记忆…

权限五张表

重点&#xff1a;权限五张表的设计 核心概念&#xff1a; 在权限管理系统中&#xff0c;经典的设计通常涉及五张表&#xff0c;分别是用户表、角色表、权限表、用户角色表和角色权限表。这五张表的设计可以有效地管理用户的权限&#xff0c;确保系统的安全性和灵活性。 用户&…

Docker-数据卷

1.数据卷 容器是隔离环境&#xff0c;容器内程序的文件、配置、运行时产生的容器都在容器内部&#xff0c;我们要读写容器内的文件非常不方便。大家思考几个问题&#xff1a; 如果要升级MySQL版本&#xff0c;需要销毁旧容器&#xff0c;那么数据岂不是跟着被销毁了&#xff1…

IT : 是工作還是嗜好? Delphi 30周年快乐!

又到2月14日了, 自从30多年前收到台湾宝蓝(Borland)公司一大包的3.5 磁盘片, 上面用黑色油性笔写着Delphi Beta开始, Delphi便和我的工作生涯有了密不可分的关系. 一年后Delphi大获成功, 自此对于使用Delphi的使用者来说2月14日也成了一个特殊的日子! 我清楚记得Delphi Beta使用…

DeepPose

目录 摘要 Abstract DeepPose 算法框架 损失函数 创新点 局限性 训练过程 代码 总结 摘要 DeepPose是首个将CNN应用于姿态估计任务的模型。该模型在传统姿态估计方法的基础上&#xff0c;通过端到端的方式直接从图像中回归出人体关键点的二维坐标&#xff0c;避免了…

[HarmonyOS]鸿蒙(添加服务卡片)推荐商品 修改卡片UI(内容)

什么是服务卡片 &#xff1f; 鸿蒙系统中的服务卡片&#xff08;Service Card&#xff09;就是一种轻量级的应用展示形式&#xff0c;它可以让用户在不打开完整应用的情况下&#xff0c;快速访问应用内的特定功能或信息。以下是服务卡片的几个关键点&#xff1a; 轻量级&#…