C语言链表简单理解
出于很多刚学习链表的很多小伙伴在学习链表的时候有很多困惑,我在此特地的出一篇博客,帮助我的朋友们。
(有写的不好的地方,大佬请勿略这篇内容)
链表里面的插入数据还是很有讲究的:
1)头插法:插入数据
1 2 3 4 5 6 7 8 9
实际输出:
9 8 7 6 5 4 3 2 1
2)尾插法:插入数据
1 2 3 4 5 6 7 8 9
实际输出:
1 2 3 4 5 6 7 8 9
由此可以发现选择插入数据的方法和实际需要的操作有选择的必要性
下面我将给出操作链表的函数代码:
例如根据下面这个结构体建立的链表:
struct stu
{
int num;
float score;
struct stu* next; /*链表访问结构体的指针,指向下一个链表节点*/
};
typedef struct stu Stu; /*自定义结构体的名称,相当于把strcut stu替换成Stu*/
头插法:
Stu* Creatlink() { /*Stu就是自定义的struct student*/
Stu* head = (Stu*)malloc(sizeof(Stu));
Stu* cur;
head->next = NULL;
while (1) {
cur = (Stu*)malloc(sizeof(Stu));
scanf("%d,%f", &cur->num, &cur->score);
cur->next = NULL;
head->next = cur;
if (cur->num == 0) break; /*这个为结束循环的判断,需要根据题目要求设置这个判断*/
}
return head;
}
尾插法:
Stu* Creatlink() { /*Stu就是自定义的struct student*/
Stu* head = NULL, * tail = NULL, * cur;
while (1) {
cur = (Stu*)malloc(sizeof(Stu));
scanf("%d,%f", &cur->num, &cur->score);
cur->next = NULL;
if (cur->num == 0) break; /*这个为结束循环的判断,需要根据题目要求设置这个判断*/
if (head == NULL) head = cur;
else tail->next = cur;
tail = cur;
}
return head;
}
删除节点:
Stu* Delnode(Stu* head, int n) {
Stu* p = head, * s = head->next;
while (s) { /*循环中删除节点的例子是不包含头节点的*/
if (s->num == n) p->next = s->next; /*if为判断删除节点的条件*/
else p = s;
s = s->next;
}
if (head->num == n) head = head->next; /*删除头节点的例子*/
return head;
}
增加节点:
Stu* Addnode(Stu* head, int n) {
Stu* p = head, * s = head->next;
Stu* cur = (Stu*)malloc(sizeof(Stu));
scanf("%d,%f", &cur->num, &cur->score); /*读入要插入的节点数据*/
while (s) {
if (s->num == n) { /*if判断插入的位置(不包含头节点位置)*/
p->next = cur;
cur->next = s;
}
else p = s;
s = s->next;
}
if (head->num == n) { /*if判断若插入位置是头部*/
cur->next = head;
head = cur;
}
return head;
}
明白以上模板,对于单链表的理解就差不多了!
链表排序:
最简单、直接的方式(直接采用冒泡或者选择排序,而且不是交换结点,只交换数据域)
//线性表的排序,采用冒泡排序,直接遍历链表
void Listsort(Node*& head) {
int i = 0;
int j = 0;
//用于变量链表
Node* L = head;
//作为一个临时量
Node* p;
Node* p1;
//如果链表为空直接返回
if (head->value == 0)return;
for (i = 0; i < head->value - 1; i++) {
L = head->next;
for (j = 0; j < head->value - i - 1; j++) {
//得到两个值
p = L;
p1 = L->next;
//如果前面的那个比后面的那个大,就交换它们之间的是数据域
if (p->value > p1->value) {
Elemtype temp = p->value;
p->value = p1->value;
p1->value = temp;
}
L = L->next;
}
}
}
因为排序中速度比较快的如快速排序是要求它们的数据域的地址是相连接的,它比较适合于顺序结构,而链式结构的时候,我们就只有使用只会进行前后两个比较多排序算法,比如冒泡排序等。我们这里是没有交换结点的一种排序方式,这种方式简单,明了,这样就是数组排序的时候是一样的。后面我会写通过交换结点的方式的排序。
下面我们就讨论讨论这个排序算法的时间复杂度了,因为它是使用冒泡排序的,它的时间只要消耗在那两重循环,所以时间复杂度为:O(n*n),这个效率实在是太低,下面我们对这个想(ˇˍˇ) 想~通过另外一种方式来实现链表的排序
————————————————
版权声明:排序算法为CSDN博主「Ouyang_Lianjun」的原创文章节选,遵循CC 4.0 BY-SA版权协议,
原文链接:https://blog.csdn.net/qq_35644234/article/details/53222603
明白以上模板,对于单链表的理解就差不多了!
后面给出一些PTA的练习题
(通过例题来理解链表编码的格式与套路)
6-6 链表拼接 (6分)
本题要求实现一个合并两个有序链表的简单函数。链表结点定义如下:
struct ListNode {
int data;
struct ListNode* next;
};
函数接口定义:
struct ListNode *mergelists(struct ListNode *list1, struct ListNode *list2);
其中list1和list2是用户传入的两个按data升序链接的链表的头指针;函数mergelists将两个链表合并成一个按data升序链接的链表,并返回结果链表的头指针。
裁判测试程序样例:
#include <stdio.h>
#include <stdlib.h>
struct ListNode {
int data;
struct ListNode* next;
};
struct ListNode* createlist(); /*裁判实现,细节不表*/
struct ListNode* mergelists(struct ListNode* list1, struct ListNode* list2);
void printlist(struct ListNode* head)
{
struct ListNode* p = head;
while (p) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
int main()
{
struct ListNode* list1, * list2;
list1 = createlist();
list2 = createlist();
list1 = mergelists(list1, list2);
printlist(list1);
return 0;
}
/* 你的代码将被嵌在这里 */
输入样例:
1 3 5 7 -1
2 4 6 -1
输出样例:
1 2 3 4 5 6 7
函数接口代码:
struct ListNode* mergelists(struct ListNode* list1, struct ListNode* list2) {
struct ListNode* p1 = list1, * p2 = list2;
int arr[100], cnt = 0;
while (p1) {
arr[cnt++] = p1->data;
p1 = p1->next;
}
while (p2) {
arr[cnt++] = p2->data;
p2 = p2->next;
}
for (int i = 0; i < cnt - 1; i++) {
for (int j = 0; j < cnt - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int exchange = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = exchange;
}
}
}
struct ListNode* head = NULL, * tail = NULL, * cur;
for (int i = 0; i < cnt; i++) {
cur = (struct ListNode*)malloc(sizeof(struct ListNode));
cur->next = NULL;
cur->data = arr[i];
if (head == NULL) head = cur;
else tail->next = cur;
tail = cur;
}
return head;
}
上面这个题目又类似于刚学习字符串时:两个字符串交叉合并的题目!
6-3 两个字符串穿插
本题要实现的程序功能是:①从键盘上先后读入两个字符串,存储在字符数组str1和str2中。注意,这两个字符串最长均可达到32个字符、最短均可为0个字符。②将字符串str2插入字符串str1中。③在屏幕上输出新生成的str1。
函数接口定义:
void conj(char *s1, char *s2);
裁判测试程序样例:
#include <stdio.h>
#include <string.h>
#define N 32
void conj(char *s1, char *s2);
int main(void)
{
char str1[N * 2], str2[N];
gets(str1);
gets(str2);
conj(str1, str2);
printf("After conj string1:%s\n", str1);
return 0;
}
/* 请在这里填写答案 */
输入样例:
123456789
abcdefghijklmn
输出样例:
1a2b3c4d5e6f7g8h9ijklmn
函数接口代码:
void conj(char* s1, char* s2) {
char s3[100];
int len1 = strlen(s1), len2 = strlen(s2);
int i = 0, j = 0, cnt = 0;
while (i < len1 && j < len2) {
s3[cnt++] = s1[i++];
s3[cnt++] = s2[j++];
}
while (i < len1) {
s3[cnt++] = *(s1 + i); i++;
}
while (j < len2) {
s3[cnt++] = *(s2 + j); j++;
}
*(s1 + 0) = '\0';
for (int i = 0; i < strlen(s3); i++) {
*(s1 + i) = s3[i];
}
*(s1 + cnt) = '\0';
}
链表的原理都是建立在数组的基础上的!
唯一需要注意的是链表地址不一定连续,而数组连续。
需要大量删除添加数据的时候链表的作用就会无限放大,因为方便,时间复杂度很低。
数组法不适合大量删除数据和添加数据,需要通过循环移动数据位置。
然而数组法适合大量修改数据,因为可以直接指令修改第n个数据,而链表必须从头节点开始到第n个数据的节点!