C的回归基础学习——数据结构(2)链表(临时)
前言
链表我是没有完整学过的,但是在学习过程中发现自己已经或多或少的见过或者用过这玩意儿了,这也说明链表是一个比较基础且常用的数据结构。这篇的重点会在基础上偏重一点
链表的实现
链表实际上是一个用处较多而且模板较为单一(至少以我现在的水平是这么认为的)所以直接上代码,以注释作为主要讲解。
#include <cstdio>
#include <cstdlib>
typedef struct student{
int score;
struct student *next;
}linkList;//定义一个链表
linkList* initList() { //初始化一个链表,实际上就是定义一个头结点
linkList* headNode = (linkList*)malloc(sizeof(linkList));
if (headNode == NULL) {
printf("空间缓存不足\n");
return headNode;
}
headNode->score = 0;
headNode->next = NULL;
return headNode;
}
/*void createList(linkList* headNode ,int n) {//建立一个链表,其中头结点中score有意义 即有n个节点
linkList* endNode ; // 其实这样会有点小问题,就是在1处添加或删除某一个元素时,需要特殊操作
endNode = headNode;
for(int i = 0;i < n ;i++)
{
scanf("%d",&endNode->score);
if (i < n - 1)//防止出现废结点,即最后一个结点
{
endNode->next = (linkList*)malloc(sizeof(linkList));
endNode = endNode->next;
}
}
endNode->next = NULL;
} */
void createList(linkList* headNode ,int n) {//建立一个链表,其中头结点中score值无意义 即有n+1个结点
linkList *endNode,*nowNode ;
endNode = headNode;
for(int i = 0;i < n ;i++)
{
nowNode = (linkList*)malloc(sizeof(linkList));
scanf("%d",&nowNode->score);
endNode->next = nowNode;
endNode = nowNode;
}
endNode->next = NULL;
}
void outList(linkList* headNode)//输出一个链表
{
linkList* nowNode=headNode->next;
while(nowNode) {
printf("%d ",nowNode->score);
nowNode = nowNode->next;
}
printf("\n");
}
void modifyList(linkList* headNode,int pos,int num) {//将第pos个结点的值修改为num
linkList* nowNode = headNode;
for(int i = 0 ;i < pos&&nowNode != NULL;i++) {
nowNode = nowNode->next;
}
if (nowNode != NULL) nowNode->score = num;
else printf("该结点不存在\n");
}
bool insertList(linkList* headNode,int pos,int inData) {//在第pos个位置插入一个结点,内值为inData
linkList* nowNode = headNode;
linkList* tmp = (linkList*)malloc(sizeof(linkList));
tmp->score = inData;
if(tmp == NULL)
{
printf("空间缓存不足\n");
return false;
}
for(int i = 0;i < pos-1&&nowNode != NULL;i++) {
nowNode = nowNode->next;
}
if(nowNode == NULL)
{
printf("结点输入过大,无法加入\n");
return false;
}
tmp->next = nowNode->next;
nowNode->next = tmp;
return true;
}
int deleteList(linkList* headNode,int pos) {//将第pos个位置的结点删除,并返回删除结点的值
linkList* nowNode = headNode;
int dataToDelete;
for(int i = 0;i < pos-1&&nowNode != NULL;i++) {
nowNode = nowNode->next;
}
if(nowNode == NULL)
{
printf("结点不存在\n");
return false;
}
linkList *nodeToDelete = nowNode->next;
dataToDelete = nodeToDelete->score;
nowNode->next = nodeToDelete->next;
free(nodeToDelete);
return dataToDelete;
}//这里为什么不用 nowNode ->next = nowNode ->next->next
//因为这样free不了内存会浪费
int main()
{
freopen("a.txt","r",stdin);
int n;
scanf("%d",&n);
linkList *test;
test = initList();
createList(test,n);
outList(test);
insertList(test,1,0);
outList(test);
deleteList(test,9);
outList(test);
deleteList(test,4);
outList(test);
modifyList(test,1,90);
outList(test);
return 0;
/*结果:
1 2 3 4 5 6
0 1 2 3 4 5 6
结点不存在
0 1 2 3 4 5 6
0 1 2 4 5 6
90 1 2 4 5 6
*/
}
其实我记得两个数组nxt[],value[]好像也可以实现简易链表??
来点实战
1.合并两个有序链表(简单)
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4 1->3->4
输出:1->1->2->3->4->4
分析:
- 原谅我不会读题!我的眼睛直接把有序这两个字给吃了。。。想了半天说难道还要链表排序。打了半天没打出来,又跑去看题解,结果简单的递归就出来了。。。
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1 == NULL) {
return l2;
}
else if(l2 == NULL) {
return l1;
}
else if(l1->val < l2->val) {
l1->next = mergeTwoLists(l1->next,l2);
return l1;
}
else {
l2->next = mergeTwoLists(l1,l2->next);
return l2;
}
}//leetcode21
- 打完后发现这玩意就是当初学归并排序的归并步骤,难道说还可以用链表来排序?!!思索一番感觉可行,但寻找中点会有点困难,此时隐约想起自己在学链表时看到的一篇关于找链表某位置的博客,翻了一翻,发现slow-fast法!!ok,没问题了,开打!上代码!
#include <cstdio>
#include <cstdlib>
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
ListNode* init() {
ListNode* headNode = (ListNode*)malloc(sizeof(ListNode));
if (headNode == NULL) {
printf("空间缓存不足\n");
return headNode;
}
headNode->val = 0;
headNode->next = NULL;
return headNode;
}
void create(ListNode* headNode) {
ListNode *endNode;
endNode = headNode;
int n;
scanf("%d",&n);
for(int i = 0;i < n;i++)
{
scanf("%d",&endNode->val);
if(i < n - 1) {
endNode->next = (ListNode*)malloc(sizeof(ListNode));
endNode = endNode->next;
}
}
endNode->next = NULL;
}
void out(ListNode* headNode)
{
ListNode* nowNode=headNode;
while(nowNode) {
printf("%d ",nowNode->val);
nowNode = nowNode->next;
}
printf("\n");
}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1 == NULL) {
return l2;
}
else if(l2 == NULL) {
return l1;
}
else if(l1->val < l2->val) {
l1->next = mergeTwoLists(l1->next,l2);
return l1;
}
else {
l2->next = mergeTwoLists(l1,l2->next);
return l2;
}
}
ListNode* Gsort(ListNode* l1) {
if(l1->next == NULL) return l1;//只有一个元素就直接返回!
ListNode *slow,*fast;
slow = l1;
fast = l1->next;//此处让fast从第二个开始是为了slow=(lenth+1)/2
while(fast&&fast->next) {
slow = slow->next;
fast = fast->next->next;
}
ListNode *right,*left;
left = l1;
right = slow->next;
slow->next = NULL;
left = Gsort(left);
right = Gsort(right);
ListNode *newList;
newList = mergeTwoLists(left, right);
return newList;
}
int main()
{
freopen("a.txt","r",stdin);
ListNode *L1;
L1 = init();
create(L1);
L1 = Gsort(L1);
out(L1);
return 0;
}
2.链表模拟加法
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
分析:
- 我一开始还以为挺简单的,就是模拟加法。但是实际操作起来。。。一共改了不下于10次才过,改到崩溃。。。
- 主要是以下几点
1.注意进位
2.注意两者为不同长度
3.注意像5+5=10这种最后进一位的情况
4.以及各种对链表的不熟练。。。
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *nowNode1,*nowNode2,*newList,*nowNode3;
nowNode1 = l1;
nowNode2 = l2;
newList = new ListNode(0);
nowNode3 = newList;
int flag = 0,num1,num2;
while(nowNode1 != NULL || nowNode2 !=NULL) {
nowNode3->next = new ListNode(0);
nowNode3 = nowNode3->next;
if (nowNode1 != NULL) num1 = nowNode1->val;
else num1 = 0;
if (nowNode2 != NULL) num2 = nowNode2->val;
else num2 = 0;
nowNode3->val = num1 + num2 + flag;
if(nowNode3->val >= 10) {
flag = nowNode3->val / 10;
nowNode3->val %= 10;
}
else {
flag = 0;
}
if (nowNode1 != NULL) nowNode1 = nowNode1->next;
if (nowNode2 != NULL) nowNode2 = nowNode2->next;
}
if (flag > 0){
nowNode3->next = new ListNode(0);
nowNode3 = nowNode3->next;
nowNode3->val = flag;
}
nowNode3->next = NULL;
return newList->next;
}
3.k个反转
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
分析就直接打在代码里了
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* newHead = new ListNode(0);
newHead->next = head;
ListNode* now = head; //反转时的当前结点
ListNode* pre = newHead;//反转链表之前的结点
ListNode* tail = head;//反转链表最后一个结点
int cnt;
while(true) {
cnt = 0;
tail = pre;
while(tail != NULL && cnt < k) {
tail = tail->next;
cnt++;
}
if(tail == NULL) break;//检测是否还剩k个 ,不够就结束
while(pre->next != tail) {
now = pre->next;
pre->next = now->next;//先将该链表最前一个元素孤立出来
now->next = tail->next;
tail->next = now;//再把now插入到tail和tail后面的结点之间
}
pre = head;//此时的head就是反转了的字符串的最后一位(原第一位)
head = pre->next;//这个地方当时想了好一会找不到比较好的处理方法,就借鉴了别人的
}
return newHead->next;
}
后记
- 现在打的代码都很冗杂,可能有一堆不需要的东西,还需要慢慢优化。
- 链表和顺序数组给我的感觉就是,一个便于修改一个便于查询,都是各有千秋的。
- 总而言之要背的模板喜加一?(其实简易链表也可以用,如邻接表什么的)