链表学习
静态链表
- 链表:结构体变量通过指针进行连接
- 代码示例:
struct Node {
int data; // 数据域
struct Node* next; // 指针域
// 指针域指向下一个结构体变量
};
- 静态链表使用不是很多,通常使用能动态申请内存的动态链表
动态链表
- 需要使用动态内存申请和模块化设计
- 模块化设计就指的是封装操作链表的功能
内存申请 (malloc)
- malloc函数:
void* malloc(unsigned size);
-
其中size是需要申请的内存大小
-
因为malloc返回的是一个无类型的指针(void*),所以使用的时候需要强制转换
-
使用完成后需要把指针所指的内存释放掉 (free),并且指针指向空 (NULL)
-
代码示例:
int *p = (int *)malloc(sizeof(int)*10);
// 这里申请了一段 40 个字节的内存空间,强制转换成 int 型 的指针赋给 *p
free(p);
// free使用的是首地址的指针,不能使用中间位置的指针
p = NULL;
// 一定要记得置空
创建动态链表
创建链表头
struct Node* createList() {
struct Node* headNode = (struct Node*)malloc(sizeof(struct Node));
// headNode 成为了结构体变量
headNode -> next = NULL;
return headNode;
// 返回这个结构体指针
}
- 链表头一般是不储存数据的
创建节点 (和创建链表头基本一样)
struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode -> data = data;
newNode -> next = NULL;
return newNode;
}
打印节点
- 通过指针来遍历整个链表
- 代码示例:
// 打印节点
void printList(struct Node* headNode) {
struct Node* pMove = headNode->next;
while (pMove) { // 判断条件为NULL时退出循环
cout << pMove->data << endl;
poMove = pMove->next;
}
}
插入方法
-
插入方法分为头插法和尾插法
-
头插法:
- 节点要插入在链表头和头节点之间的位置
- 只需要把插入的节点的 next 指向下一个节点,头节点的 next 指向要插入的节点就可以了
-
代码示例:
// 头插法
void insertNodeByHead(struct Node* headNode, int data) {
// 新建插入的节点
struct Node* newNode = createNode(data);
newNode->next = headNode->next;
headNode->next = newNode;
}
- 尾插法更简单,只需要在最后一个节点上连接要插入的节点就可以了
指定位置删除
- 越过要删除的节点,直接将上一个节点指向下一个节点
// 删除节点
void deleteNodeByAppoin(struct Node* headNode, int posData) {
struct Node* posNode = headNode->next; // 要删除的节点的指针
struct Node* posNodeFront = headNode; // 要删除的节点的前一个指针
// 判断链表是否为空
if (posNode == NULL)
cout << "链表为空,无法查找" << endl;
else
{
// 开始寻找
while (posNode->data != posData) {
posNodeFront = posNode;
posNode = posNode->next;
// 如果节点指向了空,说明指针已经指向了链表尾,也就是没有找到
if (posNode == NULL) {
cout << "未找到指定信息" << endl;
break;
}
}
// 如果找到了,开始删除
posNodeFront->next = posNode->next;
free(posNode);
posNode = NULL;
}
}
获取中间节点
- 使用快慢指针的方法,慢指针一次走一格,快指针一次走两格,一旦快指针到达末尾,慢指针指向的就是中间位置
- 代码示例:
// 获取中间节点 (只限于单数个节点状况)
struct Node* middleOddNode(Node* head)
{
Node* fast = head; // 快指针
Node* slow = head; // 慢指针
while (fast != NULL && fast->next != NULL)
{
slow = slow->next;
fast = fast->next;
fast = fast->next;// fast指针每次走两格
}
return slow;
}
// 获取中间节点 (偶数的情况)
struct Node* middleEvenNode(Node* head) {
Node* fast = head;
Node* slow = head;
while (fast != NULL && fast->next != NULL)
{
fast = fast->next;
if (fast->next == NULL) // 这里随时可以判断他是否快指针到达了链表末尾
break;
slow = slow->next;
fast = fast->next;
}
// 这里返回的是偶数情况下的中间偏前的那个节点,下一个中间节点直接.next就可以了
return slow;
}
- 主函数:
int main() {
// 新建两个链表一个为奇数,一个为偶数
struct Node* list1 = createList();
struct Node* list2 = createList();
for (int i = 0; i < 5; i++) {
insertNodeByHead(list1, i);
}
// list1.data = {0,1,2,3,4}
for (int i = 0; i < 12; i += 2) {
insertNodeByHead(list2, i);
}
// list2.data = {0,2,4,6,8,10}
struct Node* mid1 = middleNode(list1);
cout << mid1->data << endl;
struct Node* mid2 = middleNode(list2);
cout << mid2->data << mid2->next->data << endl;
return 0;
}
// 输出结果:
// 2
// 64
数据结构作业 1 详解
-
题目描述:定义两个链表,求两个链表的交集,并且把两个链表合并
-
代码详解:
1.链表的基础操作
#include<iostream>
using namespace std;
// 定义结构体
struct Node {
int data;
struct Node* next;
};
// 创建链表头
struct Node* createList() {
struct Node* headNode = (struct Node*)malloc(sizeof(struct Node));
headNode->next = NULL;
return headNode;
}
// 创建节点 (和创建链表头基本相同)
struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 头插法
void insertNodeByHead(struct Node* headNode, int data) {
struct Node* newNode = createNode(data);
newNode->next = headNode->next;
headNode->next = newNode;
}
// 打印节点
void printList(struct Node* headNode) {
struct Node* pMove = headNode->next;
while (pMove != NULL) {
cout << pMove->data << "\t";
pMove = pMove->next;
}
cout << endl;
pMove = NULL;
}
- 前三个函数是构建一个链表必不可少的三个函数,在使用时先定义一个链表头,然后使用头插法一个一个把节点插入 (创建节点的部分被在头插法里面被调用了)
2.把两个链表连接在一起
// 获取链表最后一个元素
struct Node* lastNode(struct Node* headNode) {
struct Node* tempNode;
while (true) {
tempNode = headNode;
headNode = headNode->next;
if (headNode == NULL) {
return tempNode;
}
}
}
// 合并两个链表函数 (尾插法合并两个链表)
void mergeNode(struct Node* firstNode, struct Node* secondNode) {
struct Node* tempLastNode = lastNode(firstNode);
tempLastNode->next = secondNode->next;
free(secondNode);
secondNode = NULL;
}
- 连接链表思路:
- 找到第一个链表的最后一个节点 (之前定义了一个lastNode函数)
- 然后把第二个链表除了头节点的部分都接在后面
- 然后释放第二个链表所指空间的内存,并且指针指向空
3.求两个链表的交集
// 链表长度函数
int nodeLength(struct Node* H)
{
struct Node* p = H;
int j = 0;
while (p->next != NULL)
{
j++;
p = p->next;
}
return j;
}
// 查找链表里面的值
int nodeData(struct Node* H,int mark)
{
struct Node* p = H;
int j = 0;
while (j != mark)
{
j++;
p = p->next;
}
return p->data;
}
// 获取两个链表的交集
void intersectionNode(struct Node* firstNode, struct Node* secondNode) {
int list1Length = nodeLength(firstNode);
int list2Length = nodeLength(secondNode);
for (int i = 1; i <= list1Length; i++) {
for (int j = 1; j <= list2Length; j++) {
if (nodeData(firstNode, i) == nodeData(secondNode, j)) {
cout << nodeData(firstNode, i) << "\t";
}
}
}
}
- 求两个链表的交集的思路:
- 遍历一个链表里面的数据 (用到nodeLength作为循环终止条件),每个数据和另一个链表的所有数据进行比较 (用到nodeData来读取链表里的值),如果相等则输出,如果不等则接着进行循环
4.主函数部分
int main() {
// 新建两个链表头部
struct Node* list1 = createList();
struct Node* list2 = createList();
// 使用循环给链表添加节点并赋值
for (int i = 0; i < 5; i++) {
insertNodeByHead(list1, i);
}
// list1.data = {0,1,2,3,4}
for (int i = 0; i < 10; i += 2) {
insertNodeByHead(list2, i);
}
// list2.data = {0,2,4,6,8}
// 输出两个链表
cout << "链表一:";
printList(list1);
cout << endl;
cout << "链表二:";
printList(list2);
cout << endl;
// 求两个链表交集
cout << "两个链表交集" << endl;
intersectionNode(list1, list2);
cout << endl;
// 连接两个链表
cout << "连接两个链表" << endl;
mergeNode(list1, list2);
// 因为连接完的两个链表是储存在 list1 里面的,所以输出 list1
printList(list1);
cout << endl;
return 0;
}
- 输出结果: