数据结构与算法 - 线性结构: 单链表
单链表中的每个结点不仅包含值,还包含链接到下一个结点的引用字段。通过这种方式,单链表将所有结点按顺序组织起来。
下面是一个单链表的例子:
蓝色箭头显示单个链表中的节点是如何组合到一起的.
结点结构:
// Definition for singly-linked list.
struct SinglyListNode {
int val;
struct SinglyListNode *next;
};
操作
创建一个单链表
/**
* 创建链表结点
*/
struct SinglyListNode *createNode(int value) {
struct SinglyListNode *newNode = (struct SinglyListNode *) malloc(sizeof(struct SinglyListNode));
newNode->val = value;
newNode->next = NULL;
return newNode;
}
/**
* 创建单链表
* @param arr
* @param size
* @return
*/
struct SinglyListNode *createLinkedList(int *arr, int size) {
if (size == 0) {
return NULL;
}
struct SinglyListNode *head = createNode(arr[0]);
struct SinglyListNode *curr = head;
for (int i = 1; i < size; i++) {
struct SinglyListNode *newNode = createNode(arr[i]);
curr->next = newNode;
curr = curr->next;
}
return head;
}
添加操作 - 单链表
如果我们想在给定的结点 prev 之后添加新值,我们应该:
与数组不同,我们不需要将所有元素移动到插入元素之后。因此,您可以在
O(1) 时间复杂度中将新结点插入到链表中,这非常高效。
示例
让我们在第二个结点 6 之后插入一个新的值 9 。
我们将首先初始化一个值为 9 的新结点。然后将结点 9 链接到结点 15 。最后,将结点 6 链接到结点 9 。
插入之后,我们的链表将如下所示:
在开头添加结点
众所周知,我们使用头结点来代表整个列表。
因此,在列表开头添加新节点时更新头结点 head 至关重要。
初始化一个新结点 cur ; 将新结点链接到我们的原始头结点 head。 将 cur 指定为 head 。 例如,让我们在列表的开头添加一个新结点 9 。
- 我们初始化一个新结点 9 并将其链接到当前头结点 23
- 指定结点 9 为新的头结点
/**
* 头插法
* @param head
* @param value
*/
void insertAtHead(struct SinglyListNode **head, int value) {
struct SinglyListNode *newNode = createNode(value);
newNode->next = *head;
*head = newNode;
}
/**
* 尾插法
* @param head
* @param value
*/
void insertAtTail(struct SinglyListNode **head, int value) {
struct SinglyListNode *newNode = createNode(value);
if (*head == NULL) {
*head = newNode;
} else {
struct SinglyListNode *curr = *head;
while (curr->next != NULL) {
curr = curr->next;
}
curr->next = newNode;
}
}
/**
* 随机插入
* @param head
* @param value
* @param position
*/
void insertAtPosition(struct SinglyListNode **head, int value, int position) {
if (position < 0)
position = 0;
if (position == 0) {
insertAtHead(head, value);
return;
}
struct SinglyListNode *newNode = createNode(value);
struct SinglyListNode *curr = *head;
int currentPosition = 0;
while (curr != NULL && currentPosition < position - 1) {
curr = curr->next;
currentPosition++;
}
if (curr == NULL)
return;
newNode->next = curr->next;
curr->next = newNode;
}
删除操作 - 单链表
如果我们想从单链表中删除现有结点 cur,可以分两步完成:
- 找到 cur 的上一个结点
prev及其下一个结点next
2. 接下来链接
prev 到 cur 的下一个节点 next 。
在我们的第一步中,我们需要找出 prev 和 next。使用 cur 的参考字段很容易找出 next,但是,我们必须从头结点遍历链表,以找出 prev,它的平均时间是 O(N),其中 N 是链表的长度。因此,删除结点的时间复杂度将是 O(N)。
空间复杂度为 O(1),因为我们只需要常量空间来存储指针。
示例
让我们尝试把结点 6从上面的单链表中删除。
-
从头遍历链表,直到我们找到前一个结点 prev,即结点 23
-
将 prev(结点 23)与 next(结点 15)链接
删除第一个结点
正如之前所提到的,我们使用头结点 head 来表示链表。我们的头是下面示例中的黑色结点 23。
如果想要删除第一个结点,我们可以简单地
将下一个结点分配给 head。也就是说,删除之后我们的头将会是结点 6。
/**
* 删除头结点
* @param head
*/
void deleteHead(struct SinglyListNode **head) {
if (*head == NULL)
return;
struct SinglyListNode *temp = *head;
*head = (*head)->next;
free(temp);
}
/**
* 删除尾结点
* @param head
*/
void deleteTail(struct SinglyListNode **head) {
if (*head == NULL)
return;
if ((*head)->next == NULL) {
free(*head);
*head = NULL;
return;
}
struct SinglyListNode *curr = *head;
struct SinglyListNode *prev = NULL;
while (curr->next != NULL) {
prev = curr;
curr = curr->next;
}
prev->next = NULL;
free(curr);
}
/**
* 删除中间结点
* @param head
* @param position
*/
void deleteMiddle(struct SinglyListNode **head, int position) {
if (*head == NULL || position <= 0)
return;
if (position == 1) {
deleteHead(head);
return;
}
struct SinglyListNode *curr = *head;
struct SinglyListNode *prev = NULL;
int currentPosition = 1;
while (curr != NULL && currentPosition < position) {
prev = curr;
curr = curr->next;
currentPosition++;
}
if (curr == NULL)
return;
prev->next = curr->next;
free(curr);
}
数据结构与算法 - 线性结构: 双链表
双链表以类似的方式工作,但还有一个引用字段,称为“prev”字段。有了这个额外的字段,您就能够知道当前结点的前一个结点。
双链表结点结构
struct DoublyListNode {
int val;
struct DoublyListNode *prev;
struct DoublyListNode *next;
};
操作
添加操作 - 双链表
如果我们想在现有的结点 prev 之后插入一个新的结点 cur,我们可以将此过程分为两个步骤:
- 链接 cur 与 prev 和 next,其中 next 是 prev 原始的下一个节点;
2. 用
cur 重新链接 prev 和 next
与单链表类似,添加操作的时间和空间复杂度都是
O(1)。
/**
* 头插法
* @param head
* @param value
*/
void insertAtHead(struct DoublyListNode **head, int value) {
struct DoublyListNode *newNode = createNode(value);
if (*head == NULL) {
*head = newNode;
return;
}
newNode->next = *head;
(*head)->prev = newNode;
*head = newNode;
}
/**
* 尾插法
* @param head
* @param value
*/
void insertAtTail(struct DoublyListNode **head, int value) {
struct DoublyListNode *newNode = createNode(value);
if (*head == NULL) {
*head = newNode;
return;
}
struct DoublyListNode *curr = *head;
while (curr->next != NULL) {
curr = curr->next;
}
curr->next = newNode;
newNode->prev = curr;
}
/**
* 随机插入
* @param head
* @param value
* @param position
*/
void insertAtPosition(struct DoublyListNode **head, int value, int position) {
if (position <= 0) {
insertAtHead(head, value);
return;
}
struct DoublyListNode *newNode = createNode(value);
struct DoublyListNode *curr = *head;
int currentPosition = 1;
while (curr != NULL && currentPosition < position) {
curr = curr->next;
currentPosition++;
}
if (curr == NULL) {
insertAtTail(head, value);
return;
}
newNode->next = curr;
newNode->prev = curr->prev;
curr->prev->next = newNode;
curr->prev = newNode;
}
删除操作 - 双链表
如果我们想从双链表中删除一个现有的结点 cur,我们可以简单地将它的前一个结点 prev 与下一个结点 next 链接起来。
/**
* 删除头节点
* @param head
*/
void deleteHead(struct DoublyListNode **head) {
if (*head == NULL)
return;
struct DoublyListNode *temp = *head;
*head = (*head)->next;
if (*head != NULL)
(*head)->prev = NULL;
free(temp);
}
/**
* 删除尾结点
* @param head
*/
void deleteTail(struct DoublyListNode **head) {
if (*head == NULL)
return;
struct DoublyListNode *curr = *head;
while (curr->next != NULL) {
curr = curr->next;
}
if (curr->prev != NULL)
curr->prev->next = NULL;
else
*head = NULL;
free(curr);
}
/**
* 随机删除结点
* @param head
* @param node
*/
void deleteNode(struct DoublyListNode **head, struct DoublyListNode *node) {
if (*head == NULL || node == NULL)
return;
if (node == *head) {
deleteHead(head);
return;
}
if (node->next != NULL)
node->next->prev = node->prev;
if (node->prev != NULL)
node->prev->next = node->next;
free(node);
}