链表的简单总结
这两周还是重点学习的链表,因为之前一直对指针不熟练,所以就拖了很久。
引言:
先来举一个小例子,有一个程序员他的名字是阮阮,他需要储存一段数据,所以他把这段数据的大小告诉了内存管理器,让他给自己分配一段内存
但是这段内存的大小是固定的,阮阮非常粗心,他忘记储存了一个数据,但是这个数组已经满了,没办法,他只能再向任务管理器要一段更大的内存
然后把原来的数据复制到这个新的数组中去。这样为了后面还能继续储存没办法,只能像管理员要一块很大的地方。这样就造成了浪费,那么怎么解决
这个浪费问题呢?
这里就要涉及到链表这个东西了,链表在我的理解就是在堆中没有连续的内存块,用指针来把各个内存块连在一起。这样,我需要插入一个数据,就多创建一个节点。
把然后再通过指针把这个节点插入到链表中,这样就不会像阮阮一样造成内存浪费了。
一,链表的基本结构:
struct Node { int data; struct Node* next; };
struct Node* head;
链表分为两个部分,一个部分用来储存数据,另一个部分储存着下一个,节点的地址。这个链表用一个struct来表示,int只是data储存的变量练习,也可以是char
后面就是指向下一个地址的指针。还要有一个head指向链表头,这里先大体介绍一下,详细后面再说。
二,数组与链表:
其实链表和数组是没有所谓的优劣之分了,只是在不同的应用场景下有不同的作用。
1,首先,在遍历访问数据方面,数组每一个数据占4个字节(整型),但是在链表中,因为有两个部分数据域和指针域,所以占8个字节所以在访问数据上
数组胜出(这里好像可以用时间复杂度来考虑,但是我还不是很会)。
2,然后我觉得链表对于内存的利用更加的高效,不会像数组一样浪费内存,在pta做题的时候,为了不会数组溢出,一般都会把数组设置的特别大,在储存
比较多的数据的时候,为了避免内存的浪费,还是优先选用链表。而且数组大小是固定的,不想链表可以分成很多个内存块那么灵活。
3,然后就是数据的处理方面,假设我现在要在一个数组的中间插入一个数,那么我需要把这个数之后的所有数都往后移动一格,然后再把这个数插入到这个
位置,时间复杂度为O(n/2),而用链表的话就是O(1),所以链表再数据的处理上有一定优势。
三,在链表中插入数据:
先上代码
#include <stdio.h> #include <stdlib.h>
struct Node { int data; struct Node* next; }; struct Node* head; void Insert(char data, int n); void Print(); void Delete(int n); int main() { head = NULL; Insert(1, 1); Insert(2, 2); Insert(5, 3); Insert(6, 4); Insert(7, 5); int n; scanf_s("%d", &n); Delete(n); Print(); return 0; } void Insert(int data, int n) { struct Node* temp = (struct Node*)malloc(sizeof(struct Node)); temp->data = data; temp->next = NULL;//这里就是创建一个临时链表了
if (n == 1) { head = temp; return; } struct Node* temp1 = head; for (int i = 0; i < n - 2; i++) { temp1 = temp1->next; } temp->next = temp1->next; temp1->next = temp; } void Print() { struct Node* temp = head; while (temp != NULL) { printf("%c", temp->data); temp = temp->next; } printf("\n"); }
现在来解释一下这段代码:
重点看这个Insert函数:
首先,创建一个节点,在堆中分配一个结点大小的内存给这个节点(感觉描述的怪怪的),然后把数据加到这个结点的数据域中,然后把把这个temp->next
置为NULL。然后判断插入的位置是不是第一个位置,如果是第一个位置的话head就是temp,然后再创建一个结点,进行遍历这个链表,把temp2->next的值
赋给temp2。假设我要插入n的位置,那就让temp2遍历到n-1的位置,一共遍历n-2次。到了这个位置后,把temp2->next指向的值赋给temp->next,然后temp2-
>next= temp,也就是把temp2的指针指向那个要插入结点的首地址。
print函数比较简单,这里就不做过多描述了。
四,就是链表的删除:
我这里用一个detele函数来进行删除元素。
void Delete(int n) { struct Node* temp1 = head; if (n == 1) { head = temp1->next;//如果是1的话就是把temp给跳过去所以这个地方用next
return;//这个地方也可以用else来替代
} int i; for (i = 0; i < n - 2; i++) { temp1 = temp1->next; } struct Node* temp2 = temp1->next;//temp的位置是n-1所以这个temp2就是要删除的n的那个位置;
temp1->next = temp2->next;//把temp2下一个赋给temp1的位置的next这样就可以做到跳过temp2
free(temp2); }
就说一下大意把,解释在代码中,就是创建一个temp1所指向的结点,然后把temp2跳过并且释放掉他的内存。
下次博客打算写一下链表的进阶(像反转之类的),还要去学习(其实已经有学了只是太多不好写所以多分几次)。