1.C语言实现
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 定义一个节点类
typedef struct Node {
int data;// 数据域
struct Node* next;// 指针域
}Node;
// 定义初始化链表方法
Node* initList() {
Node* list = (Node*)malloc(sizeof(Node));
list->data = 0;// 虚拟头节点的数据域储存的是链表的长度
list->next = list;// 虚拟头节点自己指向自己
return list;
}
// 定义头插方法
void headInsert(int data, Node* list) {
Node* newHead = (Node*)malloc(sizeof(Node));
newHead->data = data;
newHead->next = list->next;
list->next = newHead;
// 更新链表长度
list->data++;
}
// 定义尾插方法
void tailInsert(int data, Node* list) {
Node* node = (Node*)malloc(sizeof(Node));
// 寻找链表的尾节点
Node* tail = list;
while (tail->next != list) {
tail = tail->next;
}
node->data = data;
node->next = list;
tail->next = node;
// 更新链表长度
list->data++;
}
// 定义删除方法
bool delete(int data, Node* list) {
Node* pre = list;
Node* cur = list->next;
while (cur != list) {
if (cur->data == data) {
pre->next = cur->next;
free(cur);
// 更新链表长度
list->data--;
// 通过返回值true标识找到了第一个符合条件的节点 标识删除成功
return true;
}
pre = cur;
cur = cur->next;
}
// 如果遍历了整个链表都没有找到指定值对应的节点的话 那么就说明删除失败
return false;
}
// 定义打印方法
void printList(Node* list) {
Node* cur = list->next;
// 他有别于单向链表的打印方法 是因为如果你条件设置为当非空的时候停止循环的话 那么他永远都无法停止循环 而是陷入了死循环 所以你得设置为当下一个节点为虚拟头节点的时候停止循环才行
while (cur != list) {
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
// 定义一个主函数
int main() {
Node* list = initList();
headInsert(1, list);
headInsert(2, list);
headInsert(3, list);
headInsert(4, list);
tailInsert(5, list);
printList(list);// 4 3 2 1 5
if (delete(4, list) == true) {
printf("删除成功\n");
}
else {
printf("删除失败\n");
}
printList(list);// 3 2 1 5
printf("%d\n", list->data);// 4
return 0;
}
测试结果来看 这个链表是经得住考验的
2.mj版本的单向循环链表
我的设计理念是引入一个虚拟头节点来储存链表的长度 并且尾节点的下一节点指向的是实际的头节点 也就是虚拟头节点的下一个节点
其中有两种特殊情况
在添加操作中 当我们要对空链表进行添加的时候 需要考虑到待插入节点的下一节点并非前驱节点的后继节点 而是自己
在删除操作中 当我们执行完删除操作以后链表为空 需要考虑到待删除节点的前驱节点的下一节点并非待删除节点的后继节点 而是自己
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define ELEMENT_NOT_FOUND -1
// 节点类
typedef struct Node {
// 数据域
int data;
// 指针域
struct Node* next;
}Node;
// 定义一个方法 用于初始化链表
Node* initList() {
Node* list = (Node*)malloc(sizeof(Node));
list->data = 0;
list->next = list;
return list;
}
// 定义索引越界方法
void outOfBounds(int index) {
printf("索引为%d 索引越界了", index);
}
// 定义边界检查方法
void rangeCheck(int index, Node* list) {
if (index < 0 || index >= list->data)
outOfBounds(index);
}
// 定义针对添加方法边界检查方法
void rangeCheckForAdd(int index, Node* list) {
if (index < 0 || index > list->data)
outOfBounds(index);
}
// 定义根据指定索引查找节点的方法
Node* node(int index, Node* list) {
rangeCheck(index, list);
Node* cur = list->next;
for (int i = 0; i < index; ++i) {
cur = cur->next;
}
return cur;
}
// 定义添加方法
void add(int index, int data, Node* list) {
// 对参数索引进行边界检查
rangeCheckForAdd(index, list);
Node* pre = index == 0 ? list : node(index - 1, list);
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
if (list->data == 0) {
node->next = node;
}
else {
node->next = pre->next;
}
pre->next = node;
list->data++;
}
// 定义删除方法
int delete(int index, Node* list) {
// 对参数索引进行边界检查
rangeCheck(index, list);
Node* pre = index == 0 ? list : node(index - 1, list);
Node* delete = pre->next;
if (list->data == 1) {
pre->next = pre;
}
else {
pre->next = delete->next;
}
// 更新链表长度
list->data--;
// 返回待删除节点值
return delete->data;
}
// 定义一个根据指定节点值获取位置的方法
int indexOf(int data, Node* list) {
Node* cur = list->next;
for (int i = 0; i < list->data; ++i) {
if (cur->data == data)return i;
cur = cur->next;
}
return ELEMENT_NOT_FOUND;
}
// 定义一个方法 用于根据指定索引获取指定的节点值
int get(int index, Node* list) {
// 由于node方法中有对参数索引进行边界检查 所以无需额外的边界检查操作了
return node(index, list)->data;
}
// 定义一个方法 用于重置指定位置处的节点值 并且返回指定位置处的旧值
int set(int index, int newData, Node* list) {
// 由于node方法已经存在了对参数索引的边界检查 所以无需额外的进行边界检查操作了
int data = node(index, list)->data;
node(index, list)->data = newData;
return data;
}
// 定义一个方法 用于清空链表
void clear(Node* list) {
Node* cur = list->next;
Node* next = cur->next;
while (cur != list->next) {
free(cur);
cur = next;
next = cur->next;
}
list->data = 0;
}
// 定义一个方法 用于判断链表中是否包含指定节点值
bool contains(int data, Node* list) {
return indexOf(data, list) != ELEMENT_NOT_FOUND;
}
// 定义一个方法 用于判断链表是否为空
bool isEmpty(Node* list) {
return list->data == 0;
}
// 定义一个方法 用于获取链表长度
int size(Node* list) {
return list->data;
}
// 定义一个方法 用于打印链表
void printList(Node* list) {
Node* cur = list->next;
for (int i = 0; i < list->data; ++i) {
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
// 定义一个主函数
int main() {
Node* list = initList();
add(0, 1, list);
add(0, 2, list);
add(0, 3, list);
add(0, 4, list);
add(size(list), 5, list);
printList(list);// 4 3 2 1 5
printf("%d\n", delete(4, list));// 5
printList(list);// 4 3 2 1
printf("%d\n", list->data);// 4
clear(list);
printf("%d\n", list->data);// 0
return 0;
}
3.mj版本的单向循环链表(没有虚拟头节点)
值得注意的是:不允许存在同名的变量和函数 特别是其中size和size()
然后我们是将原来虚拟头节点中的链表长度和修改为全局变量 并且引入了全局变量来标识头节点
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define ELEMENT_NOT_FOUND -1
// 定义一个节点类
typedef struct Node {
// 数据域
int data;
// 指针域
struct Node* next;
}Node;
// 定义一个变量 用于标识头节点
Node* head = NULL;
// 定义一个变量 用于表示链表的长度
int size = 0;
// 定义一个方法 用于初始化链表
Node* initList() {
Node* list = NULL;
return list;
}
// 定义一个方法 用于判断索引是否越界
void outOfBounds(int index) {
printf("索引为:%d 索引越界了", index);
}
// 定义一个方法 用于进行边界检查
void rangeCheck(int index) {
if (index < 0 || index >= size)
outOfBounds(index);
}
// 定义一个方法 用于针对添加方法进行边界检查
void rangeCheckForAdd(int index) {
if (index < 0 || index > size)
outOfBounds(index);
}
// 定义一个方法 用于根据指定索引获取节点
Node* node(int index) {
// 对参数索引进行边界检查
rangeCheck(index);
Node* cur = head;
for (int i = 0; i < index; ++i) {
cur = cur->next;
}
return cur;
}
// 定义一个添加方法
/*
总体逻辑就是分成两种情况 一种是头部插入 一种是其他位置插入
对于头部插入的话 我们又可以分成两种情况 一种是对空链表进行插入 一种是普通的插入 由于两种情况的代码量是差不多的 所以推荐进行代码复用
*/
void add(int index, int data) {
// 对参数索引进行边界检查
rangeCheckForAdd(index);
if (index == 0) {
/*
普通情况的逻辑就是保存新的头节点 然后将该头节点指向旧的头节点 然后让尾节点指向新的头节点
但是特殊情况的逻辑就是
*/
Node* newHead = (Node*)malloc(sizeof(Node));
newHead->next = head;
newHead->data = data;
// 获取尾节点 获取尾节点的时候 对于特殊情况 如果通过node(size - 1)的话 是无法获取到预期节点的 所以需要进行特殊处理
Node* tail = size == 0 ? newHead : node(size - 1);
tail->next = newHead;
head = newHead;
}
else {
// 获取待插入节点的前驱节点
Node* pre = node(index - 1);
// 让待插入节点指向前驱节点的后继节点
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
node->next = pre->next;
pre->next = node;
}
// 不管上述执行的是哪一种情况 都需要更新链表长度
size++;
}
// 定义删除方法
int delete(int index) {
// 对参数索引进行边界检查
rangeCheck(index);
// 定义一个变量 用于保存待删除节点
Node* delete = head;
// 接着分成两种情况进行讨论 一种是头删 一种是其他位置删除
if (index == 0) {
// 还需要分成两种情况进行讨论 一种是删除之后链表长度为空 一种是普通情况 由于两种情况的代码量悬殊很大 所以选择进行分类讨论 而不是进行代码复用
if (size == 1) {
head = NULL;
}
else {
// 这种情况的总体逻辑就是要更新头节点的标识 而且还需要更新尾节点的下一节点
head = head->next;
Node* tail = node(size - 1);
tail->next = head;
}
}
else {
// 获取待删除节点的前驱节点
Node* pre = node(index - 1);
delete = pre->next;
pre->next = delete->next;
}
// 不管执行的是哪一种情况的话 那么就需要更新链表长度
size--;
// 返回待删除节点
return delete->data;
}
// 定义一个方法 用于指定节点值获取指定位置
int indexOf(int data) {
Node* cur = head;
for (int i = 0; i < size; ++i) {
if (cur->data == data) {
return i;
}
cur = cur->next;
}
// 如果上述代码没有返回值的话 那么说明在链表中不存在指定节点值的节点
return ELEMENT_NOT_FOUND;
}
// 定义一个方法 用于根据指定索引获取节点
int get(int index) {
return node(index)->data;
}
// 定义一个方法 用于重置指定索引处的节点值
int set(int index, int newData) {
int data = node(index)->data;
node(index)->data = newData;
return data;
}
// 定义一个方法 用于清空
void clear() {
Node* cur = head;
Node* next;
for (int i = 0; i < size; ++i) {
next = cur->next;
free(cur);
cur = next;
}
// 清空链表以后 链表长度为0
size = 0;
}
// 定义一个方法 用于判断是否包含指定节点值
bool contains(int data) {
return indexOf(data) != ELEMENT_NOT_FOUND;
}
// 定义一个方法 用于判断链表是否为空
bool isEmpty() {
return size == 0;
}
// 定义一个方法 用于获取链表长度
int getSize() {
return size;
}
// 定义一个方法 用于打印链表
void printList() {
Node* cur = head;
for (int i = 0; i < size; ++i) {
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
int main() {
Node* list = initList();
add(0, 1);
add(0, 2);
add(0, 3);
add(0, 4);
add(getSize(), 5);
printList();// 4 3 2 1 5
printf("%d\n", get(0));// 4
printf("%d\n", set(0, 6));// 4
printf("%d\n", delete(0));// 6
printList();// 3 2 1 5
clear();
printf("%d\n", getSize());// 0
return 0;
}