[데이터 구조] Paoding Jieniu, 그래픽과 텍스트의 조합으로 쉽게 시작하고 순환 연결 목록을 주도할 수 있습니다.

여기에 이미지 설명 삽입

Junxi_의 개인 홈페이지

부지런하고 아무도 기다리지 않는 세월을 격려하십시오

C/C++ 게임 개발

안녕하세요 미나님 Jun Xi_입니다 오늘은 지난번 싱글링크드리스트에 이어 대표적인 양방향 순환링크리스트에 대해 말씀드리겠습니다 이런 종류의 학습 연결 목록은 매우 중요합니다. 이해하기 쉬운 단어와 논리 다이어그램을 사용하여 이해를 돕기 위해 최선을 다할 것입니다
.

이중 연결 리스트의 선두주자

  • 다음은 주요 양방향 순환 연결 목록의 논리도입니다.
    여기에 이미지 설명 삽입

1. 단일 연결 리스트의 특성과 다름

  • 1. 양방향: 양방향은 선행 양방향 순환 연결 목록의 구조에서 연결 목록을 연결하는 두 개의 포인터가 있음을 의미합니다. 그 중 하나는 이전 노드를 가리키고 다른 포인터는 다음 노드를 가리킵니다. 다음 노드.
  • 2. 루프: 단일 연결 리스트의 테일 노드는 NULL을 가리키고 양방향 순환 연결 리스트의 테일 노드는 헤드 노드 헤드를 가리키고 헤드의 이전 노드에 대한 포인터는 테일 노드를 가리킨다(I 볼 그림과 결합 이해하지 못함)
  • 3. Take the lead: take the lead는 링크드 리스트의 헤드로서 양방향 순환 링크드 리스트에 센티넬 비트가 있다는 것을 의미합니다. 아래에 집중)

2. 노드에 저장된 요소

typedef int LTDataType;
typedef struct ListNode
{
    
    
	LTDataType data;//链表中存的值
	struct ListNode* next;//指向下一个结点的指针
	struct ListNode* prev;//指向上一个结点的指针
}ListNode;

3. 연결 리스트 기능 BuyListNode 초기화

//初始化双链表,创建节点
ListNode* BuyListNode(LTDataType x)
{
    
    
    ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));//malooc开辟一个结点的空间
    if (newnode == NULL)//判断一下是否开辟空间成功
    {
    
    
        perror("malloc failed\n");
        exit(-1);
    }
    //初始化时把两个指针都置空
    newnode->next = NULL;
    newnode->prev = NULL;
    newnode->data = x;
    return newnode;//返回创建好的链表节点
}
  • 이 단계는 비교적 간단합니다. 주석을 보고 코드가 매우 이해하기 쉽다고 생각하므로 너무 많은 설명이 필요하지 않습니다.

4. 연결 리스트의 헤드 노드 ListCreate 생성

// 创建返回链表的头结点.
ListNode* ListCreate()
{
    
    
    ListNode* phead = BuyListNode(0);//开辟结点所需动态空间
    phead->next = phead;
    phead->prev = phead;
    return phead;
    
}
  • 연결 리스트의 헤드에 센티널 비트가 있다고 소개했는데, 센트리 비트가 연결 리스트에 노드가 없을 때 헤드 포인터와 테일 포인터가 모두 자신을 가리키고 있는데 센티널 비트의 의미는 무엇입니까?

센티넬 비트란 무엇이며 센티넬 비트의 존재 의미

  • 센티넬 비트는 알고리즘 구현을 단순화하고 효율성을 높이기 위해 데이터 구조에 사용되는 특수 값 또는 노드입니다. 검색 작업에서 센티넬 위치는 경계를 넘어 찾을 수 없는 상황을 피하기 위해 적절한 위치에 미리 설정할 수 있습니다. 예를 들어 연결된 목록에서 센티넬 비트는 연결된 목록의 위치를 ​​찾는 데 사용되며 빈 연결 목록에도 센티넬 비트가 있습니다. 삽입 정렬에서 센티넬 비트는 이동 데이터의 순환 처리를 지원하는 데 사용되며 이동해야 하는 데이터를 저장할 수 있으며 후속 작업에서 덮어쓰지 않습니다. 센티넬 비트를 사용함으로써 알고리즘의 구현을 단순화할 수 있고, 경계 판단 횟수를 줄일 수 있으며, 프로그램의 실행 효율을 높일 수 있다.
  • 구체적으로 말하면 연결 목록의 의미는 무엇입니까?
  • Linked List를 사용할 때 Sentinel Bit가 없으면 Out-of-bound 접근과 빈 Linked List를 방지하기 위해 Linked List를 사용할 때마다 Null 연산을 수행해야 하므로 효율성에 큰 영향을 미칩니다. 프로그램 운영의. 그리고 이 센티널 비트가 있을 때, 이것이 빈 연결 리스트일 때, 이때의 phead는 자신을 가리키는 노드이므로 오류가 발생하지 않을 것이고, 연결 리스트에 노드가 있을 때 그것은 자신을 가리키는 노드이다. 헤드 노드에 루프에도 편리합니다. 테일 노드의 다음 노드를 이 phead로 직접 가리킬 수 있습니다.

5. 연결 리스트를 출력하는 ListPrint 함수

// 双向链表打印
void ListPrint(ListNode* phead)
{
    
    
    assert(phead);//断言,判断是否有结点,
    printf("Phead");//没有其他结点,打印phead
    ListNode* cur = phead->next;     
    while (cur!= phead)
    {
    
    
        printf(" <==> %d", cur->data);
        cur = cur->next;

    }
    printf("\n");
}
  • 기본적으로 이전 단일 연결 리스트의 출력과 동일하지만 우리는 순환 연결 리스트이기 때문에 연결 리스트의 꼬리는 더 이상 NULL을 가리키지 않습니다. 는 phead를 가리키므로 연결 리스트를 순회할 때 연결 리스트가 다시 phead와 같을 때 연결 리스트가 한 번 순회했음을 의미하며 순회할 때 루프가 중지되는 조건입니다.

6. 연결 리스트를 파괴하는 ListDestory 함수

// 双向链表销毁
void ListDestory(ListNode* phead)
{
    
    
    assert(phead);

    ListNode* cur = phead->next;
    while (cur!=phead) 
    {
    
    
        ListNode* next = cur->next;
        free(cur);
        cur = next;
    }
    free(phead);
       
    
}
  • 연결 리스트 전체를 순회하고 각 노드를 해제하며, 연결 리스트를 순회하는 루프 조건은 위의 연결 리스트 출력과 동일합니다.
  • 자, 위의 기본 인터페이스에 대해 이야기한 후 다음 단계는 하이라이트입니다. 연결된 목록에 노드를 삽입하는 방법은 무엇입니까?

7. Linked List의 헤드 삽입 및 헤드 삭제

  • 사실 삽입과 삭제에 관해서는 이전 단일 연결 리스트의 삽입과 삭제를 이해할 수 있다면 이 작품의 논리는 사실 이해하기 매우 쉽습니다.

플러그

// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x)
{
    
    
    assert(phead);//断言,
    
    ListNode* frist = BuyListNode(x);//创建节点
    ListNode* cur = phead->next;
    frist->next = cur;
    frist->prev = phead;
    cur->prev = frist;
    phead->next = frist;

}
  • 여기서 우리가 이루고자 하는 목표를 사진을 통해 먼저 살펴보자
    여기에 이미지 설명 삽입
  • 首先,我们需要保存一下第一个位置的地址,也就是图中的cur,然后我们让first的next指向此时的cur,first的prev指向头head,这样就建立起了结点间的联系
    여기에 이미지 설명 삽입
  • 但是由于我们需要链表是一个双头链表,我们此时还需要让head的next指向插入的first,cur的prev也指向first这样才算真正完成我们的头插
    여기에 이미지 설명 삽입

헤드 삭제

// 双向链表头删
void ListPopFront(ListNode* phead)
{
    
    
    assert(phead);
    ListNode* frist = phead->next;
   
    phead->next = frist->next;
    frist->next->prev = phead;
    free(frist);
}
  • phead의 next가 first의 다음 노드를 가리키도록 하고, first의 다음 노드의 prev가 phead를 가리키도록 하고 이때 free를 해제하고 head를 삭제합니다.
  • 헤드플러그와 유사하게 위의 논리도를 거꾸로 보면 되므로 도면을 그리느라 공간을 낭비하지 않아도 됩니다.

8. Linked List 테일 삽입 및 삭제

테일 플러그

// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
    
    
    assert(phead);
    assert(phead->prev);
    ListNode* newnode = BuyListNode(x);
    //ListInsert(phead, x);
    ListNode* tail = phead->prev;//起初的尾
    newnode->next = phead;//插入的结点指向头
    tail->next = newnode;//之前的尾指向现在的尾
    newnode->prev = tail;
    tail = newnode;
   
}
  • 不同于单链表的是,由于我们的链表是循环链表,其实尾部的结点就保存在phead的prev中,因此不再需要遍历就能轻易的找到我们的尾部
    여기에 이미지 설명 삽입
  • 此时,就比较简单啦,我们让现在的尾(d3)的next指向插入的newnode,让newnode的prev指向d3,next指向phead,再让phead的prev指向插入的newnode,即可完成尾插

여기에 이미지 설명 삽입

꼬리 삭제

// 双向链表尾删
void ListPopBack(ListNode* phead)
{
    
    
    assert(phead);
    assert(phead->prev);
    ListNode* tail = phead->prev;
    ListNode* tailprev = phead->prev->prev;
    tailprev->next = phead;
    tail = tailprev;
    free(phead->prev);  
   // ListErase(phead->prev);

}
  • 꼬리를 삭제하려면 현재 꼬리의 이전 노드를 찾아 현재 꼬리로 바꾸고 마지막으로 현재 꼬리를 해제해야 합니다.
    여기에 이미지 설명 삽입
    여기에 이미지 설명 삽입

9. 연결된 목록에 값이 있는지 확인하는 ListFind 함수

// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
    
    
    assert(phead);
   
    ListNode* cur = phead;
    while (cur->next != phead)
    {
    
    
        if (cur->data == x)
        	return cur;
        
        else
         cur = cur->next;
    }
    
    return NULL;
}
  • 이 또한 매우 간단하여 연결 리스트를 순회하여 찾고자 하는 값과 노드에 저장된 값을 비교하여 찾으면 현재 노드 위치의 주소를 반환하고 찾지 못하면 NULL을 반환한다.

10. pos의 이전 위치 값을 수정하는 ListInsert 함수

  • 여기서 pos 위치의 값을 수정하는 기능에 대해 간단히 말씀드리자면 매우 간단하여 공간을 낭비할 필요가 없습니다 위의 ListFind를 통해 찾아낸 후 바로 이 위치의 데이터를 x 필요합니다.
cur->data=x;
  • 제목 속으로
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
    
    
    
    
    ListNode* newnode = BuyListNode(x);//创建结点
    ListNode* posPrev = pos->prev;
    newnode->next = posPrev->next;
    posPrev->next = newnode;
    pos->prev = newnode;
    newnode->prev = posPrev;
    

}
  • 여기서 pos 위치는 위의 조회 기능으로 찾을 수 있습니다.
  • 찾아보면 아주 간단합니다 pos의 위치 이전 노드 posprev의 다음 노드를 newnode로 가리키고, newnode의 prev가 posprev를 가리키게 한 다음 newnode의 다음 노드가 pos를 가리키도록 하고 마지막으로 pos의 prev를 가리키도록 합니다. 삽입된 newnode를 가리킵니다.
    여기에 이미지 설명 삽입
    여기에 이미지 설명 삽입

11. 위치 pos에서 노드 ListErase를 삭제합니다.

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
    
    
    assert(pos);
    ListNode* posPrev = pos->prev;
    ListNode* posNext = pos->next;
    posPrev->next = posNext;
    posNext->prev = posPrev;
    
    free(pos);
    //pos = NULL;
}

  • 이것은 또한 매우 이해하기 쉽습니다. 우리는 pos의 이전 위치에 있는 노드 posPrev가 pos 다음 위치에 있는 노드 posNext를 가리키도록 합니다.
    여기에 이미지 설명 삽입
    여기에 이미지 설명 삽입
  • 마지막 소소한 디테일 최적화, 면접이나 필기시험 등 제한된 시간에 있을 때 앞선 양방향 순환 연결 리스트를 완성해야 합니다. 꼬리 삭제, 위의 내용을 참조하십시오. 이 두 가지 기능은
//头插与头删
ListInsert(phead->next, x);
ListErase(phead->next);
//尾插与尾删
ListInsert(phead, x);
ListErase(phead->prev);
  • 왜 그런지 생각해봐
  • 사실 우리의 head-plug 삭제와 tail-plug 삭제는 특별한 pos 위치에 대한 연산에 지나지 않으므로 자연스럽게 위의 두 기능을 참조할 수 있습니다.

요약하다

  • 오늘의 내용은 여기까지입니다. 양방향 순환 연결 리스트에서 주도권을 잡는 것은 이해하기 쉽지 않을 수 있습니다. 정말 깊이 이해하고 싶다면 논리도를 가지고 두드려 봐야 합니다. 연습하지 않고 배워라 오! !
  • 그럼 궁금하신 점은 댓글이나 비밀댓글로 물어봐주세요 다음에 또 만나요!

초보 블로거가 만들기 쉽지 않으니 글의 내용이 도움이 되셨다면 이 초보 블로거를 클릭하셔서 떠나시는 것도 좋을 것 같습니다. 귀하의 지원은 업데이트에 대한 동기 부여입니다! ! !

**(케리 블로거 3연속 응원 부탁드립니다!!! 아래 댓글을 눌러 좋아요를 누르고 케리를 도와주세요)**

여기에 이미지 설명 삽입

추천

출처blog.csdn.net/syf666250/article/details/132132380