🎄数据结构C实现
🍭头文件大纲
//
// Created by Liu Xianmeng on 2023/10/1 13:47
//
# include <stdio.h>
# include <stdlib.h>
#ifndef CDATASTRCUTRUES_20230930_SINGLELINKEDLIST_H
#define CDATASTRCUTRUES_20230930_SINGLELINKEDLIST_H
// 定义单链表的结点
typedef struct SingleListNode {
int data;
struct SingleListNode *next;
}SingleListNode;
// 定义单链表本身
typedef struct SingleList {
int size; // 实际大小(存储元素的数目)
SingleListNode *head; // 单链表的头结点
}SingleList;
/**
* 初始化单链表
* @param singleList
* @return
*/
SingleList* initList(SingleList *singleList) { ... }
/**
* 向单链表插入元素
* @param singleList 单链表
* @param pos 插入元素的位置 下标位置
* @param e 要插入的元素
* @return 插入位置不合法 返回-1 插入成功返回1
*/
int insertOne(SingleList *singleList, int pos, int data) { ... }
/**
* 获取单链表的第pos个元素
* @param singleList 带dummy哑结点的单链表(第一个head节点不存储数据)
* @param pos 获取单链表的第pos个元素 -> position位置 非下标位置
* @param e 接收单链表的第pos个元素
* @return 成功获取返回1 未获取到返回0
*/
int getElem(SingleList *singleList, int pos, int *e) { ... }
/**
* 从链表中删除pos位置的元素
* @param L 带有虚拟头结点的单链表
* @param pos pos是元素的序号
* @param e 接收被删除的值
* @return 成功删除返回1 删除失败返回0
*/
int deleteOne(SingleList *L, int pos, int *e) { ... }
/**
* 合并两个有序的链表
* @param L1
* @param L2
* @return 返回合并的链表L3
*/
SingleList* mergeTwoList(SingleList *L1, SingleList *L2) { ... }
#endif //CDATASTRCUTRUES_20230930_SINGLELINKEDLIST_H
🍭实现初始化函数
/**
* 初始化单链表 对传入的单链表指针进行初始化并返回
* @param singleList 传入的单链表指针
* @return 返回已经初始化的单链表
*/
SingleList* initList(SingleList *singleList) {
// 申请单链表对象的存储空间
singleList = (SingleList *) malloc(sizeof(SingleList));
singleList->size = 0;
// 申请单链表中头结点的存储空间
singleList->head = (SingleListNode *)malloc(sizeof(SingleListNode));
// 分配空间失败返回NULL
if(!singleList->head) return NULL;
// 初始化单链表头结点的域
singleList->head->data = -1;
singleList->head->next = NULL;
// 初始化成功返回指针singleList
return singleList;
}
🍭实现insertOne函数
如果不指定插入的位置 可以编写一个重载的方法默认插入到链表的尾部
/**
* 向单链表插入单个元素
* @param singleList 单链表
* @param pos 插入元素的位置 下标位置
* @param e 要插入的元素
* @return 插入位置不合法 返回-1 插入成功返回1
*/
int insertOne(SingleList *singleList, int pos, int data) {
// 如果插入的位置不合法 返回-1
if(pos < 0 || pos > singleList->size) {
return -1;
}
/////////// 接下来执行插入操作 关键是找到插入位置的上一个位置 ///////////
// 1 先用指针p指向头结点
SingleListNode *p = singleList->head;
// 2 然后向后移动pos个位置
for(int i = 1; i <= pos; ++i) p = p->next;
// 3 执行插入
// 3.1 先创建一个新的结点并分配空间 初始化数据
SingleListNode *curNode;
curNode = (SingleListNode *)malloc(sizeof(SingleListNode));
curNode->data = data;
// 3.2 执行插入
curNode->next = p->next;
p->next = curNode;
// 3.3 更新singleList的size大小
++singleList->size;
return 1; // 成功插入数据返回1
}
🍭实现getElem函数
/**
* 获取单链表的第pos个元素
* @param singleList 带dummy哑结点的单链表(第一个head节点不存储数据)
* @param pos 获取单链表的第pos个元素 -> position位置 非下标位置
* @param e 接收单链表的第pos个元素
* @return 成功获取返回1 未获取到返回0
*/
int getElem(SingleList *singleList, int pos, int *e) {
// 链表的元素个数不足pos个 获取不到第pos个元素 返回0
if(singleList->size < pos) return 0;
SingleListNode *p; // 声明一个Node指针 用来遍历元素
p = singleList->head->next; // 从第一个存储数据的结点开始遍历
int count = 1; // 计数到pos
while(count != pos) {
p = p->next;
++count;
}// while循环结束后 p指向目标结点
// 获取结果
*e = p->data;
return 1; // 成功获取到数据返回1
}
⚡阶段测试1
//
// Created by Liu Xianmeng on 2023/10/1 13:47
//
# include <stdio.h>
# include "SingleLinkedList.h"
int main() {
SingleList *singleList;
singleList = initList(singleList);
//////////////// 初始化成功 继续往下走 //////////////
insertOne(singleList, 0, 10);
insertOne(singleList, 1, 20);
insertOne(singleList, 2, 30);
int num;
// 依次获取第3、1、2个数据
getElem(singleList, 3, &num);
printf("getElem(singleList, 1, &getOneNum) -> 获取到的数字是:%d\n", num);
getElem(singleList, 1, &num);
printf("getElem(singleList, 1, &getOneNum) -> 获取到的数字是:%d\n", num);
getElem(singleList, 2, &num);
printf("getElem(singleList, 1, &getOneNum) -> 获取到的数字是:%d\n", num);
return 0;
}
执行结果如下:
🍭实现deleteOne函数
注意删除后需要将被删除的结点的存储空间🎯释放掉
/**
* 从链表中删除pos位置的元素
* @param L 带有虚拟头结点的单链表
* @param pos pos是元素的序号
* @param e 接收被删除的值
* @return 成功删除返回1 删除失败返回0
*/
int deleteOne(SingleList *L, int pos, int *e) {
// 验证序号不合法 返回0
if(pos - 1 < 0 || pos - 1 > L->size - 1) return 0;
// 指针指向虚拟头结点
SingleListNode *p = L->head;
// 指针向后移动pos-1个位置
for(int i = 1; i<= pos - 1; ++i) p = p->next;
// *e接收被删除的值
*e = p->next->data;
// toFreeNode指针暂时指向被删除的结点
SingleListNode *toFreeNode = p->next;
// 执行删除操作
p->next = p->next->next;
// 🎯释放被删除的结点的存储空间
free(toFreeNode);
// 返回结果
return 1; // 成功删除
}
⚡阶段测试2
//
// Created by Liu Xianmeng on 2023/10/1 13:47
//
# include <stdio.h>
# include "SingleLinkedList.h"
int main() {
SingleList *singleList;
singleList = initList(singleList);
//////////////// 初始化成功 继续往下走 //////////////
insertOne(singleList, 0, 10);
insertOne(singleList, 1, 20);
insertOne(singleList, 2, 30);
int num;
deleteOne(singleList, 1, &num);
printf("deleteOne(singleList, 1, &num) -> 被删除的数字是:%d\n", num);
getElem(singleList, 1, &num);
printf("getElem(singleList, 1, &getOneNum) -> 获取到的数字是:%d\n", num);
return 0;
}
🍭实现mergeTwoList函数
合并两个有序链表 实现的函数中暂不考虑结点的内存释放问题
/**
* 合并两个有序的链表
* @param L1
* @param L2
* @return 返回合并的链表L3
*/
SingleList* mergeTwoList(SingleList *L1, SingleList *L2) {
// 初始化要返回的结果链表
SingleList *L3 = (SingleList *) malloc(sizeof(SingleList));
L3->head = (SingleListNode *) malloc(sizeof(SingleListNode));
L3->head->next = NULL;
L3->size = L1->size + L2->size; // 直接把L3的size设置为L1和L2的size之和
// 定义三个指针分别来遍历L1、L2、L3
SingleListNode *p1 = L1->head->next;
SingleListNode *p2 = L2->head->next;
SingleListNode *p3 = L3->head;
SingleListNode *next = NULL;
while(p1 != NULL && p2 != NULL){
// 如果当前p1指向的数更小 则合并p1
if(p1->data <= p2->data) {
// 暂存p1的next
next = p1->next;
// p1的next指向NULL
p1->next = NULL;
// p3合并当前的p1结点
p3->next = p1;
p3 = p3->next;
// 更新下一个p1结点
p1 = next;
} else { // 否则 合并p2
// 暂存p1的next
next = p2->next;
// p2的next指向NULL
p2->next = NULL;
// p3合并当前的p2结点
p3->next = p2;
p3 = p3->next;
// 更新下一个p2结点
p2 = next;
}
}
// while循环退出后 只能确定有一个链表被合并完了
// 需要两个if进行核查是不是还有一个链表没有被合并完 如果没合并完则追加到p3的末尾
if(p1 != NULL) p3->next = p1;
if(p2 != NULL) p3->next = p2;
// 返回合并结果
return L3;
}
⚡阶段测试3
//
// Created by Liu Xianmeng on 2023/10/1 13:47
//
# include <stdio.h>
# include "SingleLinkedList.h"
int main() {
// 初始化测试链表1
SingleList *singleList;
singleList = initList(singleList);
insertOne(singleList, 0, 10);
insertOne(singleList, 1, 20);
insertOne(singleList, 2, 30);
// 初始化测试链表2
SingleList *singleList2;
singleList2 = initList(singleList2);
insertOne(singleList2, 0, 16);
insertOne(singleList2, 1, 26);
insertOne(singleList2, 2, 36);
// 执行合并
SingleList *rst = mergeTwoList(singleList, singleList2);
printf("断点调试");
return 0;
}
🎄题目题解
📑牛客 删除有序链表中重复的元素-II (java题解)
给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素
给出的链表为1→2→3→3→4→4→5, 返回1→2→5. 给出的链表为1→1→1→2→3, 返回2→3.
数据范围:链表长度 0≤n≤100000≤n≤10000,链表中的值满足 ∣val∣≤1000∣val∣≤1000
要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)
进阶:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
public static ListNode deleteDuplicates (ListNode head) {}
}
⚡分析
只需要遍历一遍即可求解该题目 需要使用一个标记变量
boolean flag
标记当前遍历的元素当前节点是不是只出现1次 初始化为true
表示之前没有出现过每当遍历到一个元素A的时候 首先考虑它是不是原链表的最后一个元素
算法分析如下:
while循环遍历head链表的所有元素{
if( 1.1当前元素是原链表的最后一个元素){
if( 1.1.1 当前元素是否已经出现过){
如果出现过 则直接返回结果✅
} else 1.1.2 当前元素之前没有出现过{
则将当前元素加入要返回的链表的尾部
返回链表✅
}
} else 1.2当前元素不是最后一个{
if(1.2.1 当前元素已经出现过){
currentNode = nextNode;
} else 1.2.2 当前元素没有出现过{
if(1.2.2.1 判断当前元素和下一个元素是否相同){
相同则currentNode = nextNode;
} else 1.2.2.2 不同 {
则将当前元素加入要返回的链表的尾部
flag = true;
currentNode = nextNode;
}
}
}
}
返回最终结果✅
具体实现:
import java.util.*;
public class Solution {
public static ListNode deleteDuplicates (ListNode head) {
// 要返回的结果链表(哑结点)
ListNode dummy = new ListNode(-1);
// 指向要返回的结果链表的尾部
ListNode rear = dummy;
// 当前节点是不是只出现1次 默认只出现1次
boolean flag = true;
// 循环遍历head链表的所有元素
while(head != null) {
ListNode next = head.next;
if(next == null) { 🎯1.1
if(flag == true) { 🎯1.1.1
/******* 拼接一个节点 *******/
rear.next = head;
rear = head;
// 返回结果
return dummy.next;
} else { 🎯1.1.2
return dummy.next;
}
} else { // next不为null 🎯1.2
if(next.val == head.val) { // head === next 🎯1.2.1
head = next;
flag = false; // 当前节点出现不止一次
} else { // head != next 🎯1.2.2
if(flag == false) { 🎯1.2.2.1
head = next;
flag = true; // 恢复当前只出现1次
} else { // flag = true 🎯1.2.2.2
/******* 拼接一个节点 *******/
head.next = null;
rear.next = head;
rear = head;
head = next;
}
}
}
}
// 返回结果 这个地方被执行的可能只有一种 就是传入的链表本身为空
// 否则函数一定会在while循环体内返回
return dummy.next;
}
}