Data structure 4: Doubly linked list + OJ question

Table of contents

Creation of doubly linked list

 The structure definition of the doubly linked list:

Create the head node of the doubly linked list:

Creation of doubly linked list nodes:

Printing of doubly linked list:

tail insertion of doubly linked list

tail deletion of doubly linked list

 Doubly linked list plug

doubly linked list lookup

 The doubly linked list is inserted in front of pos

Doubly linked list deletes the node at position pos

Linked list OJ problem

Linked list partition

Palindromic structure of linked list

intersecting list

Ring list 1

Ring List 2

copy linked list with random pointers


 The two-way circular linked list is actually very similar to a small game:

 Are you familiar with it? The logic of the loop is actually the same as the effect of the double arrow going to the end and returning to the beginning .

 The logical structure of the doubly linked list is shown in the figure above, which looks much more complicated than the one-way linked list, but it is precisely because of its complex structure that it is more convenient for us to deal with some of the details.

Creation of doubly linked list

 Interface for adding, deleting, checking and modifying:

ListNode* creatspace(DataType x);


// 创建返回链表的头结点.

ListNode* ListCreate();

// 双向链表销毁

void ListDestory(ListNode* phead);

// 双向链表打印

void ListPrint(ListNode* phead);

// 双向链表尾插

void ListPushBack(ListNode* phead, DataType x);

// 双向链表尾删

void ListPopBack(ListNode* phead);

// 双向链表头插

void ListPushFront(ListNode* phead, DataType x);

// 双向链表头删

void ListPopFront(ListNode* phead);

// 双向链表查找

ListNode* ListFind(ListNode* phead, DataType x);

// 双向链表在pos的前面进行插入

void ListInsert(ListNode* pos, DataType x);

// 双向链表删除pos位置的节点

void ListErase(ListNode* pos);

 The structure definition of the doubly linked list:

typedef struct ListNode
{
	DataType data;

	struct ListNode* prev;//指向前一个节点
	struct ListNode* next;//指向后一个节点

}ListNode;

Create the head node of the doubly linked list:

Same as the one-way linked list, create a node and link it, but the structure of the head of the double-linked list is a little more complicated. In order to realize the loop, its front pointer and back pointer need to point to itself.

The head node does not need to store values

// 创建返回链表的头结点.

ListNode* ListCreate()
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		printf("malloc fail");
		exit(-1);
	}

	newnode->prev = newnode;
	newnode->next = newnode;
	return newnode;
}


Creation of doubly linked list nodes:

ListNode* Creatspace(DataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		printf ("malloc fail");
		exit(-1);
	}

	newnode->data = x;
	newnode->prev = NULL;
	newnode->next = NULL;

	return newnode;
}

The logic is the same as the node of the one-way linked list, open up a new space, store the value, and return a structure pointer.


Printing of doubly linked list:

 The termination of the printing cycle is because of the loop, we can no longer use NULL as a sign of the end of the linked list, but the role of the head node is reflected, it is only necessary to stop the loop until the head pointer is stopped, in order to enter the loop successfully, cur is no longer initialized For phead, but the next of phead.

// 双向链表打印

void ListPrint(ListNode* phead)
{
	if(phead != NULL)
		printf("head<=>");


	ListNode* cur = phead->next;

	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}

	printf("NULL\n");
}

tail insertion of doubly linked list

 When inserting at the end, create a node, and the new node is linked to the previous node, that is, the next of the previous node points to the new node, the prev of the new node points to the previous node, and the next of the new node inserted at the end must be the head node.

Tail insertion is much more convenient than a one-way linked list. Due to the existence of loops, we don't need to traverse the entire linked list to find the tail node

// 双向链表尾插

void ListPushBack(ListNode* phead, DataType x)
{
	
	assert(phead);

	ListNode* newnode = Creatspace(x);

	ListNode* prev = phead->prev;

	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = phead;
	phead->prev = newnode;

}


tail deletion of doubly linked list

 Create a tool pointer, and link the new tail node to the head node when the tail node is deleted.

// 双向链表尾删

void ListPopBack(ListNode* phead)
{
	assert(phead);

	ListNode* cur  = phead->prev;
	ListNode* prev = cur->prev;

	free(cur);
	cur = NULL;

	prev->next = phead;
	phead->prev = prev;


}


 Doubly linked list plug

 The logic of head insertion is quite simple, just insert it directly behind the head node

// 双向链表头插

void ListPushFront(ListNode* phead, DataType x)
{
	assert(phead);

	ListNode* newnode = Creatspace(x);

	newnode->next = phead->next;
	newnode->prev = phead;
	phead->next->prev = newnode;
	phead->next = newnode;


}


doubly linked list lookup

 Still traversing the entire linked list, if not found, return NULL directly

// 双向链表查找

ListNode* ListFind(ListNode* phead, DataType x)
{
	assert(phead);

	ListNode* cur = phead->next;

	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}


	return NULL;
}


 The doubly linked list is inserted in front of pos

 Insertion can be realized by cooperating with the previous search

// 双向链表在pos的前面进行插入

void ListInsert(ListNode* pos, DataType x)
{

	ListNode* prev = pos->prev;

	ListNode* newnode = Creatspace(x);

	prev->next = newnode;
	newnode->prev = newnode;

	newnode->next = pos;
	pos->prev = newnode;


}


Doubly linked list deletes the node at position pos

// 双向链表删除pos位置的节点

void ListErase(ListNode* pos)
{
	ListNode* prev = pos->prev;
	prev->next = pos->next;
	pos->next->prev = prev;

	free(pos);
	pos = NULL;

}


In this way, a doubly linked list that implements the basic addition, deletion, query and modification functions is successfully created.

Linked list OJ problem

Linked list partition

 Link: Linked List Split_Niuke Topic_Niuke.com

 In fact, this question will become very easy after specific analysis, but before doing this question, we first throw out a new concept, the sentinel node.

In fact, it is the head node of the doubly linked list. It does not store data and always points to the first data. It is only destroyed when the entire linked list is destroyed. So here the sentinel bit refers to a single-item linked list node that does not store data and always points to the first node that stores data.

 There are many advantages to defining such a sentinel bit. First of all, the problem of using a secondary pointer is eliminated, because we do not need to change the node pointed to by the pHead pointer, only need to change the next of the sentinel bit, and look for the header Nodes will be much easier and there will be no such problem of losing the position of the head node.

Why introduce the concept of sentinel bit here? Because the title requires that we cannot change the original order of the linked list, if we simply take the small end and insert the big end, we will change the entire original order. So in order to avoid this problem, we can create two linked lists to store values ​​greater than x and values ​​less than x separately, and then link them.

 The existence of sentinel bits can easily allow us to find the head node.

According to the general logic, we can write code like this:

class Partition {
  public:
    ListNode* partition(ListNode* pHead, int x) {
        // write code here
        struct ListNode* BigGuard,*Bigcur,*SmallGurad,*Smallcur;
        BigGuard = Bigcur = (struct ListNode*)malloc (sizeof(struct ListNode));
        SmallGurad = Smallcur = (struct ListNode*)malloc (sizeof(struct ListNode));


        struct ListNode* cur = pHead;

        while (cur)
        {
            if(cur -> val >= x)
            {
                Bigcur->next  = cur;
                Bigcur = Bigcur->next;
            }
            else
            {
                Smallcur->next = cur;
                Smallcur = Smallcur->next;
            }
            cur = cur ->next;
        }

        Smallcur->next = BigGuard->next;



        free(BigGuard);
        free(SmallGurad);

        pHead =SmallGurad->next;
        return pHead;
   }
};

But it's impossible to live like this, why?

We also need to consider the following three extreme cases:

 The first is the problem of the empty linked list . If an empty linked list is passed in, we will have problems when linking the linked list of the same size.

 Smallcur->next = BigGuard->next;

 The condition of while is that cur is not empty. At this time, while is not entered, the next of these sentinel bits are not initialized, and they are all random values.

To prevent this, we also add initialization.


  //空链表进不去while,以免产生随机值
        BigGuard->next = NULL;
        SmallGurad -> next = NULL;

Of course, there is also the problem that the next point of the last node of the big linked list is not empty. We only need to empty the next of bigcur between the two linked lists.

        Smallcur->next = BigGuard->next;
        Bigcur ->next = NULL;

Palindromic structure of linked list

Link: Palindrome Structure of Linked List

 This question needs to deal with odd and even numbers. It may seem complicated, but we can make full use of the questions we wrote earlier to make this question easier.

Our most basic logic is: find the middle node, reverse the nodes behind the middle node, and compare from the beginning from the middle node.

Then the answers to the questions about reversing the linked list and finding intermediate nodes that we wrote earlier can be used again.

class PalindromeList {
  public:

    struct ListNode* reverseList(struct ListNode* head) {

        struct ListNode* cur =  head;
        struct ListNode* next = NULL;
        struct ListNode* newhead = NULL;

        while (cur) {
            next = cur->next;

            cur -> next = newhead;
            newhead = cur ;
            cur = next;

        }
        return newhead;

    }
    struct ListNode* middleNode(struct ListNode* head) {
        struct ListNode* slow = head;
        struct ListNode* fast = head;

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

        return slow;
    }

    bool chkPalindrome(ListNode* A) {
        // write code here
      ListNode* mid =   middleNode(A);
      ListNode* rmid =  reverseList(mid);

      while (A && rmid)
      {

          if(A->val != rmid->val )
          {
            return false;
          }
        A=A->next;
        rmid = rmid ->next;
      }
       return true;
    }
};

The head pointer and the intermediate node pointer only need one of them to be empty, and the loop ends. In this way, whether it is an odd number or an even number, you don’t need to make a special judgment, because when the entire linked list is odd, due to the coincidence of inversion, the head pointer It will not point to the intermediate node but point to empty. At this time, the number of comparisons in the entire linked list is equal anyway.

 


intersecting list

 Link: Lituo

 There are two ways to solve this problem:

 We use the second method to solve:

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {


        struct ListNode * cura = headA;
        struct ListNode * curb = headB;

        int count1 = 0;
        int count2 = 0;

        while(cura)
        {
            cura = cura->next;
            ++count1;
        }
        while(curb)
        {
            curb = curb->next;
            ++count2;
        }
        if(cura != curb)
        {
            return NULL;
        }
        int n =abs(count1-count2);

        if(count1<count2)
        {
            while(n--)
            {
                headB = headB->next;
            }
        }
        else 
        {
            while(n--)
            {
                headA = headA->next;
            }
        }
        while(headA)
        {
            if(headA != headB)
            {
            headA= headA->next;
            headB= headB->next;
            }
            else
            {
                return  headA;
            }
        }
        return NULL;
}

Ring list 1

Link: Lituo

 

This problem can use fast and slow pointers, that is, the slow pointer takes one step at a time, and the fast pointer takes two steps at a time. The two pointers start to move from the actual position of the linked list. If the linked list has a ring, they will definitely meet in the ring, otherwise the fast pointer will go first. end of the linked list.
 

bool hasCycle(struct ListNode *head) {
    struct ListNode * fast = head;
    struct ListNode * slow = head;

    while(fast && fast->next )
    {
        fast = fast->next->next;
        slow = slow->next;

        if(fast == slow)
        {
            return true;
        }
    }

    return false;

}

Ring List 2

 Link: Lituo

 The code for this question is relatively simple. Let me talk about the conclusion first. It is not difficult to write according to the conclusion. What is difficult to understand is the derivation of the conclusion.

Conclusion: Let a pointer traverse the linked list from the starting position of the linked list, and at the same time let a pointer run around the circle from the position of the meeting point when the ring is judged.
Both pointers take one step each time, and they will definitely be at the entry point in the end. meet.

The derivation process:

We first assume that there is a large enough linked list, which has a large enough ring

We create two pointers, one for slow and one for fast, with a step size of 1 for slow and double that for fast.

Suppose the length before entering the ring is L

ring length C

The distance from the entrance to the meeting point is X

Assume that before slow enters the ring, fast has already turned N circles in the ring, N>=1

A is the starting point of the linked list

 Then the distance traveled by fast is: X + L +N*C

 slow is: L + X

Then when the distance traveled by the two pointers is equal, it is the meeting point

The expression is thus listed: 2(L+X) = X + L +N*C

Then:                   L + X = N * C

                                     L = N*C - X

The expression is converted into a language description to be the conclusion: one pointer starts from the meeting point, and the other pointer starts from the starting point of the linked list synchronously, and the step size is 1. When they meet, the point is the starting point of the ring.

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode * fast = head;
    struct ListNode * slow = head;
    struct ListNode * meet = head;
    struct ListNode * start = head;


    while(fast&&fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        if(slow == fast)
        {
            meet  = slow;
            while(start != meet)
            {
                start = start->next;
                meet = meet->next;
            }
            return meet;
        }

    }

    return NULL;
}

copy linked list with random pointers

 Link: Lituo

 This question is still a bit difficult, drawing analysis is very important, and the logic principle is not difficult, but in order to achieve deep copy, we also need to create a new linked list.

1. Copy the original node first

 2. Update the random value of each copy node

 

 3. Integrate the created copy node into a new linked list

 


So far, the detailed description of the OJ question of the doubly linked list is over

Thanks for reading! Hope to help you a little bit!

 

Guess you like

Origin blog.csdn.net/m0_53607711/article/details/127179766