链表
概述:
是一种数据结构
分为单链表与双链表两种
单链表:
链表种节点是离散的在内存中开辟空间的 因为是离散开辟,内存地址通常不是连续的,地址不一定相邻,甚至可能存在其他数据在它们之间。
双链表
1 定义节点
分析:
一个节点就是一个结构体变量。
如:
单链表节点
typedef struct node { //数据域 结构体成员变量; //int age ;..... //地址域 Node *next; }Node;
双链表节点
但是Node别名 是在下面定义的 内部怎么使用呢
typedef struct node { //数据域 结构体成员变量; //地址域 // Node *next; // 但是 Node别名 是在下面定义的 内部怎么使用呢 // Node *head; // 使用别名就会报错 //地址域 //解决 使用全名 也就是结构体 加结构体名 struct node *next; struct node *head; }Node;
2 组建链表节点
2.1 静态组建节点
#include <stdio.h> // 静态组建双链表节点 typedef struct node { // 数据域 int num; // 地址域 struct node *next; struct node *head; } Node; int main(int argc, char const *argv[]) { Node n01 = {1, NULL, NULL}; // 节点1 Node n02 = {2, NULL, NULL}; // 节点2 Node n03 = {3, NULL, NULL}; // 节点3 n01.next = &n02; n02.head = &n01; // 1 2 链表进行关联上了 n02.next = &n03; n03.head = &n02; // 2 3 链表进行关联上了 // 链表遍历 void print_link(Node * head) { // 将首节点 赋值给当前节点 Node *n = head; // 判断当前节点是否为NULL while (n != NULL) { // 当前节点不为空,打印其数据 printf("%d\n", n->num); // 移动当前节点到下一个节点 n = n->next; } } print_link(&n01); printf("==============\n"); print_link(&n02); return 0; }
2.2 动态组建节点【*】
可以动态的操作 也就是局限性小
有序的 是指存的顺序 和取出顺序一样
就是怎么存的 怎么取的
比如 4 2 1 3 取出的时候 顺序也是 4 2 1 3
因为动态组建链表节点 是 使用 指针 所以
节点之间的链接方式,不应该使用取地址 & 操作,而应该直接使用节点指针。
> n01->next = &n02; 错误的
> n01->next = n02; 正确的
动态创建链表节点是和静态的创建节点是有区别的
#include <stdio.h> #include <stdlib.h> // 动态组建双链表节点 typedef struct node { // 数据域 int num; // 地址域 struct node *next; struct node *head; } Node; int main(int argc, char const *argv[]) { Node *n01 = (Node *)calloc(1, sizeof(Node)); // 节点1 Node *n02 = (Node *)calloc(1, sizeof(Node)); // 节点2 Node *n03 = (Node *)calloc(1, sizeof(Node)); // 节点3 // 判空 if (n01 == NULL || n02 == NULL || n03 == NULL) { return 0; } // 首先 头节点置空 n01->head = NULL; // 1 链表的头节点置空 // 节点之间的链接方式,不应该使用取地址 & 操作,而应该直接使用节点指针。 n01->next = &n02; 错误的 n01->next = n02; // 1 2 链表就关联上了 n02->next = n03; n03->head = n02; // 2 3 链表就关联上了 // 尾节点置空 n03->next = NULL; n01->num = 1; n02->num = 2; n03->num = 3; // 链表遍历 void print_link(Node * head) { // 将首节点 赋值给当前节点 Node *n = head; // 判断当前节点是否为NULL while (1) { // 当前节点不为空,打印其数据 printf("%d\n", n->num); // 移动当前节点到下一个节点 if (n->next == NULL) { break; } n = n->next; } } // 遍历链表 方式2 void print_link1(Node * head) { Node *jiedian = head; while (jiedian != NULL) // 只要有 节点就进行遍历 { // 打印节点数据 printf("节点数据为 %d\n", jiedian->num); // 节点往后移动一个 jiedian = jiedian->next; } } printf("=======\n"); print_link(n01); // 传递的参数应该是节点指针 p,而不是节点指针的地址 &p。 print_link1(n01); printf("=======\n"); print_link(n03); printf("=======\n"); print_link(n02); // 手动释放内存 free(n01); free(n02); free(n03); return 0; }
3 链表的操作
3.1 添加:
目的: 链表尾部增加
所需条件: 1 给谁添加 (首节点 head)
2 要添加的节点 newNode
函数:
/* 参数: head : 链表的第一个位置 就是首节点 newNode: 要添加的节点 返回值 : 链表的首节点 */ // 链表的添加 Node *add_node(Node *head, Node *newNode) { if (head == NULL) { head = newNode; return head; } Node *n = head; // n 为设置的 当前节点 while (n->next != NULL) { n = n->next; // 将当前节点移动到下一节点 } // 当循环结束后n就是最后一个节点 【要是单链表就结束了】 n->next = newNode; // 将新节点的上一个节点设置为原来的最后一个节点 【双链表结束】 newNode->head = n; return head; }
优化:
/* 参数: head : 链表的第一个位置 就是首节点 newNode: 要添加的节点 返回值 : 链表的首节点 */ Node* add_node(Node *head,Node *newNode) { if(head == NULL) { head = newNode; return head; } Node *n = head; //n 为设置的 当前节点 while( n->next != NULL) { n = n->next; //将当前节点移动到下一节点 } //当循环结束后n就是最后一个节点 【要是单链表就结束了】 n->next = newNode; //将新节点的上一个节点设置为原来的最后一个节点 【双链表结束】 newNode->head = n; return head; }
3.2 遍历
/* 参数: head :要遍历的链表的首节点 */ void print_link(Node *head) // 遍历节点 { // 将首节点 赋值给当前节点 Node *n = head; // 判断当前节点是否为NULL while (n != NULL) { // 当前节点不为空,打印其数据 printf("%d\n", n->num); // 移动当前节点到下一个节点 n = n->next; } }
3.3 删除
/* 参数: head: 头节点 delNode:要删除的节点 返回值: 删除后链表的首节点 作用: 删除指定节点 */ Node *del_node(Node *head, Node *delNode) { // 判断是否有首节点 if (head == NULL) { printf("没有首节点\n"); return NULL; } // 判断要删除的节点是不是空的 if (delNode == NULL) { printf("要删除的节点为NULL\n"); return head; } // 当要删除的节点就是首节点 if (head == delNode) { // 要删除的节点为首节点 // 将下一个节点设为首节点 head = head->next; // 将 先在的首节点的头节点 置空 head->head = NULL; // 释放原来的首节点 free(delNode); return head; } // 要删除的节点不是首节点,此时寻找要删除的节点 // n 当前节点 Node *n = head; while (n != delNode && n != NULL) { n = n->next; } if (n == NULL) { printf("没有要删除的节点"); } // 判断要删除的节点就是最后一个节点的时候 if (n->next == NULL) { Node *headNode = n->head; headNode->next = NULL; free(n); return head; } // n就是要删除的节点 Node *headNode = n->head; // 取出n的上一个节点 Node *nextNode = n->next; // 取出n的下 一个节点 free(n); // 释放n headNode->next = nextNode; // 让n的上一个节点的下一个节点为n的下一个节点 nextNode->head = headNode; // 让n的下一个节点的下一个节点为n的上一个节点 return head; }
3.4 修改
/* 参数: head:首节点 num:查找的值 newNum:修改后的值 返回值: 无 作用: 按值 按位 修改指定节点的值
// 按值 修改 #include <stdio.h> void update_link(Node *head, int num, int newNum) { Node *n = find_link(head, num); if (n == NULL) { return; } n->num = newNum; }
/* 作用: 按位置修改指定节点的值 参数: head:首节点 index:修改的位置 newNum:修改后的值 返回值: 无 */ void update_i(Node *head, int index, int newNum) { Node *n = find_i(head, index); if (n == NULL) { return; } n->num = newNum; }
3.5 插入
目的:随意位置插入
/* 参数: head: 首节点 index 要插入的节点位置 newNode 新节点 返回值: 插入后首节点 作用: 按位置插入新节点 */ // 思路 按你想要插入的位置插入 Node *insertlink(Node *head, int index, Node *newNode) { if (index == 0) { newNdode->next = head; head->head = newNode; head = newNode; return head; } // index 位置原来的节点 Node *n = find_index_link(head, index) { Node *n = find_index_link(head, index); if (n == NULL) { add_link(head, newNode); // add_link 是添加 链表节点 函数 return head; } Node *headNode = n->head; headNode->next = newNode; newNode->head = headNode; newNode->next = n; n->head = newNode; return head; } }
获取链表长度
/* 参数: 首节点 作用: 获取链表长度 返回值: 链表长度 */ int get_len(Node *head) { int i = 0; Node *n = head; while (n != NULL) { i++; n = n->next; } return i; }
3.6 查找
按位置进行查找节点
- 1 按值查找
- 2 按位查找
/* 作用: 按结构体中存储的值查找对应的节点 参数: head:首节点 num:查找的值 返回值: 查询到的节点,如果为NULL证明不存在 */
//【 1 按值查找 】 Node * find_link(Node * head,int num) { Ndoe *n =head; while(n != NULL) { if(n-> num == num) { return n; } n = n->next; } return NULL; }
// 【 2 按位查找 】 /* 作用: 按结构体在链表中的位置查找对应的节点 参数: head:首节点 index:下标 返回值: 查询到的节点,如果为NULL证明不存在 */ Node *find_i(Node *head, int index) { Node *n = head; int i = 0; while (n != NULL) { if (i == index) { return n; } n = n->next; i++; } return NULL; }
3.7 逆序
#include <stdio.h> // 逆序排列 void print_link_r(Node *head) { int len = get_len(head); int index = len - 1; // 下标就是长度减一 就是最后一位的下标 Node *n = find_i(head, index); while (n != NULL) { printf("%d\n", n->num); n = n->head; } }
3.8 排序
顺序
Node *sort_link(Node *head)
{
if (head == NULL)
{
return NULL;
}
int len = get_len(head);
for (int i = 0; i < len; i++)
{
for (int j = 0; j < len - 1; j++)
{
Node *n = find_i(head, j);
Node *nn = n->next;
if (n->num > nn->num)
{
if (j == 0)
{
Node *nnn = nn->next;
nn->next = n;
n->head = nn;
nn->head = NULL;
n->next = nnn;
nnn->head = n;
head = nn;
}
else if (j == len - 2)
{
Node *hn = n->head; // 倒数第三个节点
hn->next = nn;
nn->head = hn;
nn->next = n;
n->head = nn;
n->next = NULL;
}
else
{
Node *hn = n->head;
Node *nnn = nn->next;
hn->next = nn;
nn->head = hn;
nn->next = n;
n->head = nn;
n->next = nnn;
nnn->head = n;
}
}
}
}
return head;
}
逆序排列
//逆序排列
void print_link_desc(Node *head)
{
int len = get_len(head);
int index =len-1 ; // 下标就是长度减一 就是最后一位的下标
Node *n = find_i(head,index);
while (n != NULL)
{
printf("%d\n",n->num);
n = n->head;
}
}
3.9 释放
#include <stdio.h>
void free_link(Node *head)
{
Node *n = head;
while (n != NULL)
{
Node *nextNode = n->next;
free(n);
n = nextNode;
}
}