[Data structure] Sequence list and linked list

Sequence lists and linked lists

1. Linear table

A linear list (linear list) is a finite sequence of n data elements with the same characteristics. Linear table is a data structure widely used in practice. Common linear tables: sequential list, linked list, stack, queue, string…

A linear table is logically a linear structure, that is, a continuous straight line. However, the physical structure is not necessarily continuous. When linear tables are physically stored, they are usually stored in the form of arrays and linked structures.

2. Sequence table

2.1 Concept and structure

A sequence table is a linear structure that uses a storage unit with continuous physical addresses to store data elements in sequence. Generally, array storage is used.

Store. Complete the addition, deletion, checking and modification of data on the array.

The sequence table can generally be divided into:

\1. Static sequence list: Use fixed-length arrays to store elements.

\2. Dynamic sequence table: Use dynamically opened array storage.

2.2 Interface implementation

Static sequence tables are only suitable for scenarios where you know how much data needs to be stored. The fixed-length array of the static sequence table causes N to be too large. If the space is too much, it is wasted, and if it is too little, it will not be enough. Therefore, in reality, dynamic sequence tables are basically used to dynamically allocate space according to needs, so below we implement dynamic sequence tables.

typedef int SLDataType;
//Dynamic storage of sequence table
typedef struct SeqList
{<!-- -->
  SLDataType* array; // Points to a dynamically allocated array
  size_t size; // Number of valid data
  size_t capacity; //The size of the capacity space
}SeqList;
//Basic add, delete, check and modify interface
//Sequence table initialization

void SeqListInit(SeqList* psl);
// Check the space, if it is full, increase the capacity
void CheckCapacity(SeqList* psl);
//Insert at the end of the sequence table
void SeqListPushBack(SeqList* psl, SLDataType x);
//Delete at the end of the sequence table
void SeqListPopBack(SeqList* psl);
// Insert sequence header
void SeqListPushFront(SeqList* psl, SLDataType x);
//Delete sequence header
void SeqListPopFront(SeqList* psl);
//Sequence table lookup
int SeqListFind(SeqList* psl, SLDataType x);
// The sequence table inserts x at position pos
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
//Delete the value of pos position in the sequence table
void SeqListErase(SeqList* psl, size_t pos);
//Sequence list destroyed
void SeqListDestory(SeqList* psl);
// Print sequence table
void SeqListPrint(SeqList* psl);

Head plug implementation:

The implementation of head insertion requires first moving the data in the sequence table back one by one, and then inserting the data into the position with subscript 0.

Header deletion implementation:

Head deletion only needs to overwrite the data in the sequence table one by one. Note that you need to reduce the effective number in the sequence table by 1, that is, ps->size-1.

The sequence table inserts x at position pos:

Delete the value of pos position in the sequence table:

2.3 Sequence table expansion

There are two situations when the sequence table is expanded using realloc:

  1. There is enough memory space at the back: expand in place, and the starting address remains unchanged after expansion.
  2. The back space is not enough for expansion: remote expansion, the starting address changes after expansion.

2.4 Questions and Thoughts on Sequence Lists

question:

advantage:

  1. End insertion and end deletion are fast enough
  2. Random access and modification of subscripts

shortcoming:

  1. The efficiency of insertion and deletion in the middle/head is too low, and the time complexity is O(N)
  2. Capacity expansion (especially off-site expansion) requires applying for new space, copying data, and releasing old space. There will be a lot of consumption.
  3. Expansion is generally a 2-fold increase, and there will inevitably be a certain amount of wasted space. For example, the current capacity is 100, and when it is full, it will be expanded to 200. If we continue to insert 5 data, and no data will be inserted later, 95 data spaces will be wasted.

Thinking: How to solve the shortcomings of the above problems? The structure of the linked list is given below to take a look.

3. Linked list

3.1 The concept and structure of linked lists

Concept: A linked list is a physically non-continuous and non-sequential storage structure. The logical sequence of data elements is through pointer links in the linked list. strong>Achieved sequentially.

Physical structure:

Notice:

  1. As can be seen from the above figure, the chain structure is logically continuous, but not necessarily physically continuous.
  2. In reality, nodes are generally applied from the heap.
  3. The space requested from the heap is allocated according to a certain strategy. The space applied for twice may be continuous or discontinuous.

Assuming that on a 32-bit system, the value field in the node is of type int, then the size of a node is 8 bytes, then there may also be the following linked list:

3.2 Classification of linked lists

In practice, the structures of linked lists are very diverse. There are 8 types of linked list structures when combined with the following situations:

\1. One-way or two-way

\2. Take the lead or not

\3. Loop or non-loop

Although there are so many linked list structures, there are two structures we most commonly use in practice:

reason

  1. Headless one-way acyclic linked list: simple structure, generally not used to store data alone. In practice, it is more often used as a substructure of other data structures, such as hash buckets, adjacency lists of graphs, etc. In addition, this structure appears a lot in written interviews.
  2. Headed two-way circular linked list: the most complex structure, generally used to store data separately. The linked list data structures used in practice are all headed bidirectional circular linked lists. In addition, although this structure is complex, after using the code to implement it, you will find that the structure will bring many advantages, and the implementation will be simple. We will know it later when we implement the code.

3.3 Implementation of one-way linked list

// 1. Headless + one-way + non-cyclic linked list implementation of addition, deletion, query and modification
typedef int SLTDateType;
typedef struct SListNode
{<!-- -->
 SLTDateType data;
 struct SListNode* next;
}SListNode;
// Dynamically apply for a node
SListNode* BuySListNode(SLTDateType x);
//Singly linked list printing
void SListPrint(SListNode* plist);
//Insert at the end of single linked list
void SListPushBack(SListNode** pplist, SLTDateType x);
// Header of singly linked list
void SListPushFront(SListNode** pplist, SLTDateType x);
//Tail deletion of singly linked list
void SListPopBack(SListNode** pplist);
//Delete single linked list header
void SListPopFront(SListNode** pplist);
//Singly linked list search
SListNode* SListFind(SListNode* plist, SLTDateType x);
//Insert x after pos position in singly linked list
// Analyze and think about why not insert before pos position?
void SListInsertAfter(SListNode* pos, SLTDateType x);
//Singly linked list deletes the value after pos position
// Analyze and think about why the pos position is not deleted?
void SListEraseAfter(SListNode* pos);

3.4 Interface implementation of one-way linked list

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"


//Print
void SLTPrint(SLTNode*phead)
{<!-- -->
SLTNode* cur = phead;
//while (cur != NULL)
while (cur)
{<!-- -->
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\\
");
}

SLTNode* BuySListNode(SLTDataType x)
{<!-- -->
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{<!-- -->
perror("malloc fail");
exit(-1);
}

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

return newnode;

}

End insertion phead is the formal parameter of plist //There is a linked list at the beginning
//void SLTPushBack(SLTNode* phead, SLTDataType x)
//{<!-- -->
// SLTNode* newnode = BuySListNode(x);
// SLTNode* tail = phead;
// while (tail->next != NULL)
// {<!-- -->
// tail = tail->next;
// }
// tail->next = newnode;
//}

//Tail insert //Including no linked list at the beginning
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{<!-- -->
//pphead does not exist, so it must be asserted
assert(pphead);

SLTNode* newnode = BuySListNode(x);
if (*pphead == NULL)
{<!-- -->
//Change the pointer of the structure, so use a secondary pointer
*pphead = newnode;
}
else
{<!-- -->
SLTNode* tail = *pphead;
while (tail->next != NULL)
{<!-- -->
tail = tail->next;
}
//To change the structure, use the pointer of the structure.
tail->next = newnode;
}
}

//Head plug
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{<!-- -->
//pphead does not exist, so it must be asserted
assert(pphead);

SLTNode* newnode = BuySListNode(x);

newnode->next = *pphead;
*pphead = newnode;
}

//Delete tail
void SLTPopBack(SLTNode** pphead)
{<!-- -->
//There is no empty situation for pphead, so it must be asserted
assert(pphead);

//1.Empty
assert(*pphead);
//2. A node
//3. More than one node
if ((*pphead)->next == NULL)
{<!-- -->
free(*pphead);
*pphead = NULL;
}
else
{<!-- -->
\t\t//method 1.
SLTNode* tailPrev = NULL;
SLTNode* tail = *pphead;
while (tail->next)
{<!-- -->
tailPrev = tail;
tail = tail->next;
}
free(tail);
tailPrev->next = NULL;
\t\t
Method 2.
//SLTNode* tail = *pphead;
//while (tail->next->next)
//{<!-- -->
// tail = tail->next;
//}
//free(tail->next);
//tail->next = NULL;
}
}

//Delete header
void SLTPopFront(SLTNode** pphead)
{<!-- -->
//pphead does not exist, so it must be asserted
assert(pphead);

\t//null
assert(*pphead);

\t//non empty
SLTNode* newhead = (*pphead)->next;
free(*pphead);
*pphead = newhead;
}

//Find if there is a number x, find it and return a pointer to the number
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{<!-- -->
SLTNode* cur = phead;
while (cur)
{<!-- -->
if (cur->data == x)
{<!-- -->
return cur;
}
cur = cur->next;
}
return NULL;
}

//Insert before pos position
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{<!-- -->
//pphead does not exist, so it must be asserted
assert(pphead);
assert(pos);

if (pos == *pphead)
{<!-- -->
SLTPushFront(pphead, x);
}
else
{<!-- -->
SLTNode* prev = *pphead;
while (prev->next != pos)
{<!-- -->
prev = prev->next;
}
SLTNode* newnode = BuySListNode(x);
prev->next = newnode;
newnode->next = pos;
}
}

//Insert after pos position
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{<!-- -->
assert(pos);

SLTNode* newnode = BuySListNode(x);
\t
//The following two sentences cannot exchange positions, otherwise they will form a loop.
newnode->next = pos->next;
pos->next = newnode;
}

//Delete pos position
void SLTErase(SLTNode** pphead, SLTNode* pos)
{<!-- -->
//pphead does not exist, so it must be asserted
assert(pphead);
assert(pos);

if (*pphead == pos)
{<!-- -->
SLTPopFront(pphead);
}
//else if (pos->next == NULL)
//{<!-- -->
//SLTPopBack(pphead);
//}
else
{<!-- -->
SLTNode* prev = *pphead;
while (prev->next != pos)
{<!-- -->
prev = prev->next;
}
//free(prev->next);//Don’t free, otherwise this node will be gone
prev->next = pos->next;
}
}

//Delete the position after pos
void SLTEraseAfter(SLTNode* pos)
{<!-- -->
//
assert(pos);

//Check whether pos is the tail node
//assert(pos->next);//Violent detection
if (pos->next == NULL)//Gentle detection
{<!-- -->
return NULL;
}

SLTNode* posNext = pos->next;
pos->next = posNext->next;
free(posNext);
posNext = NULL;

}


//Without header, delete pos position
void SLTEraseNoFront(SLTNode* pos)
{<!-- -->
//Check whether pos is the tail node
//assert(pos->next);//Violent detection
if (pos->next == NULL)//Gentle detection
{<!-- -->
return NULL;
}

SLTNode* posNext = pos->next;
pos->data = posNext->data;
pos->next = posNext->next;
free(posNext);
posNext = NULL;
}

void SLTDestroy(SLTNode** pphead)
{<!-- -->
assert(pphead);

SLTNode* cur = *pphead;
while (cur)
{<!-- -->
SLTNode* next = cur->next;
free(cur);

cur = next;
}
*pphead = NULL;
}

3.5 Implementation of doubly linked list

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

// 2. Take the lead + two-way + circular linked list implementation of addition, deletion, checking and modification
typedef int LTDataType;
typedef struct ListNode
{<!-- -->
 LTDataType_data;
 struct ListNode* next;
 struct ListNode* prev;
}ListNode;
//Create the head node of the returned linked list.
ListNode* ListCreate();
// Destroy doubly linked list
void ListDestory(ListNode* plist);
//Print doubly linked list
void ListPrint(ListNode* plist);
//Insert at the end of the doubly linked list
void ListPushBack(ListNode* plist, LTDataType x);
// Delete the end of the doubly linked list
void ListPopBack(ListNode* plist);
// Insert the head of the two-way linked list
void ListPushFront(ListNode* plist, LTDataType x);
// Delete the header of the doubly linked list
void ListPopFront(ListNode* plist);
//Doubly linked list search
ListNode* ListFind(ListNode* plist, LTDataType x);
// Doubly linked list is inserted in front of pos
void ListInsert(ListNode* pos, LTDataType x);
// Delete the node at pos position in the doubly linked list
void ListErase(ListNode* pos);
//List size
int LTSize(LTNode* phead);
//Find pos location
LTNode* LTFind (LTNode* phead, LTDataType x);
//destroy
LTNode* LTDetory(LTNode* phead);

3.6 Interface implementation of doubly linked list

LTNode* BuyLTNode(LTDataType x)
{<!-- -->
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{<!-- -->
perror("malloc fail");
exit(-1);
}

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

return node;
}

LTNode* LTInit()
{<!-- -->
LTNode* phead = BuyLTNode(0);
phead->next = phead;
phead->prev = phead;

return phead;
}
//Print
void LTPrint(LTNode*phead)
{<!-- -->
assert(phead);

printf("phead<=>");
LTNode* cur = phead->next;
while (cur != phead)
{<!-- -->
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\\
");
}
//Tail insertion
void LTPushBack(LTNode* phead, LTDataType x)
{<!-- -->
assert(phead);

//LTNode* tail = phead->prev;
//LTNode* newnode = BuyLTNode(x);

//newnode->prev = tail;
//tail->next = newnode;

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

//Insert in front of the phead position (that is, the end of the linked list), which is equivalent to tail insertion
LTInsert(phead, x);
}
//Delete tail
void LTPopBack(LTNode*phead)
{<!-- -->
assert(phead);
assert(phead->next != phead);

/*LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
free(tail);

tailPrev->next = phead;
phead->prev = tailPrev;*/

//Deleting the node at phead->prev position is equivalent to tail deletion
LTErase(phead->prev);
}
//Head plug
void LTPushFront(LTNode* phead, LTDataType x)
{<!-- -->
assert(phead);

//LTNode* newnode = BuyLTNode(x);
//newnode->next = phead->next;
//phead->next->prev = newnode;

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

//LTNode* newnode = BuyLTNode(x);
//LTNode* first = phead->next;

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

//Insert in front of phead->next position, which is equivalent to head insertion
LTInsert(phead->next, x);
}
//Delete header
void LTPopFront(LTNode* phead)
{<!-- -->
assert(phead);
assert(phead->next != phead);

//LTNode* first = phead->next;
//LTNode* second = first->next;

//free(first);

//phead->next = second;
//second->prev = phead;


//Delete the node at phead->next position, which is equivalent to head deletion
LTErase(phead->next);
}

int LTSize(LTNode*phead)
{<!-- -->
assert(phead);

int size = 0;
LTNode* cur = phead->next;
while (cur != phead)
{<!-- -->
+ + size;
cur = cur->next;
}

return size;
}

//Insert forward from pos position
void LTInsert(LTNode* pos, LTDataType x)
{<!-- -->
\t
assert(pos);

LTNode* posPrev = pos->prev;
LTNode* newnode = BuyLTNode(x);

posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
\t
}

//Delete from pos position
void LTErase(LTNode* pos)
{<!-- -->

assert(pos);
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;

free(pos);

posPrev->next = posNext;
posNext->prev = posPrev;
}

LTNode* LTFind(LTNode* phead, LTDataType x)
{<!-- -->
assert(phead);

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

//destroy
LTNode* LTDetory(LTNode* phead)
{<!-- -->
assert(phead);
LTNode* cur = phead->next;

while (cur != phead)
{<!-- -->
//next = cur->next;
LTNode* next = cur->next;
free(cur);
cur = next;
}

free(phead);
}

4. The difference between sequence lists and linked lists

Note: Cache utilization refers to the storage architecture and local principles.
Extended knowledge: “CPU cache knowledge relevant to programmers” https://coolshell.cn/articles/20793.html