一、顺序存储的优缺点
1. 数据存储空间必须连续 (苛刻)
2. 插入 删除 时间效率不高O(n)
3. 通过位置找到元素
base[pos - 1] 常量级O(1)
二、链式存储的特点和核心操作逻辑
逻辑上连续
顺序存储 连续物理内存
链式存储 物理上不连续 增加 元素逻辑的关系
存储结构特点 元素里包含 数据 + 关系
顺序存储维护 整个表内容的首地址
存储密度高
链式存储维护 表内容的首节点,首节点逐个寻找其他节点
存储密度不高(要增加指针域,即找到下一个结点的钥匙,必须做出牺牲)
对于如上图所示的单链表
结点的定义如下:
struct node {
int data;
struct node *next;//指向下一个结点,故数据类型还是node,指向的只是一个地址,指针域本身包含的只是一个地址
};
struct node {
int data;
struct node next;//这样写就嵌套了,出现了自己指向自己的情况
};
对于像这种指向两个地址的结点,结点的定义如下:
struct node {
int data;
struct node *left;//左指针
struct node *right;//右指针
};
指向多个地址的结点,比如1:5(指向五个结点),定义如下:
struct node {
int data;
struct node *p[5];
};
操作算法
在第2个位置上做插入操作时,如果索引指针指向了第2个节点,无法进行后续操作
单链表中,一旦索引指针往前一走,之前的元素,就再也找不到,如下图所示,故如果要在第2个位置做插入操作,索引得在1就停下来。
一旦把节点的next赋新值,之前的下一个元素的关系,就断开了,如下图所示,就再也找不到2的地址了。
1. 插入操作
node->next = p->next;
p->next = node;
先处理新节点,再处理老节点
2. 删除操作
删除A(比如下图中的5)元素,站在A元素的前一个元素上,才能删除A元素
备份 备份A
tmp = p->next;
p->next = tmp->next;
free(tmp);
p->next = p->next->next; (不建议)
3. 遍历
顺序存储: for(int i = 0; i < 5; i++) {
data[i];
}
链式存储:
<1>方法一:
找到某个节点的next等于null
以p 结点为例
while(p) {
// 处理p所指向值
p = p->next;
}
单链表里找最大值
int max = 0;
p = xxx;
while (p) {
if (p->value > max) {
max = p->value;
}
p = p->next;
}
max
<2>方法二
第二种遍历,看下一个元素的情况,可以实现插入删除
p = xxx
while(p->next) {
if (p->next->value == 2)
break;
p = p->next;
}
p
三、单链表的实现
结构的操作:
申请一个结构的头 存储数据的首地址,以及这个结构的其他参数
结构头里面要包含一个区域,指向真实的数据
带头节点 (推荐)
不带头结点 (不推荐)
代码:
linkList.h
#ifndef LINKLIST_H
#define LINKLIST_H
/*单向链表,带头结点的表头*/
typedef int Element ;//先定义一个通用元素
//定义结点
typedef struct node{
Element ele;
struct node *next;
}Node;
//表头
typedef struct{
Node head;
int len;
}LinkList;
//生成表头
LinkList *creatLinkList();
//释放,要一个一个释放
void releaseLinkList(LinkList *linkList);
void showLinkList(LinkList *linkList);
//插入结点,在哪张表第几个位置插入什么元素
int insertLinkList(LinkList *linkList,int pos,Element e);
//删除结点---某个元素(在哪个表删除什么元素)
int deleteLinkList(LinkList *linkList,Element e);
#endif //LINKLIST_H
linkList.c
#include <stdio.h>
#include <stdlib.h>
#include "linkList.h"
LinkList *creatLinkList(){
LinkList *link = (LinkList *)malloc(sizeof(LinkList));//申请空间
if(link == NULL){
printf("malloc error!\n");
return NULL;
}
link->head.ele = 0x88;//关于结构体中什么时候用.什么时候用-> 如果是变量,用. 如果是地址,用-> head是一个实际的空间,是变量,故用.
link->head.next = NULL;//最开始的时候为空
link->len = 0;
return link;
}
void releaseLinkList(LinkList *linkList) {
int cnt = 0;//看看释放了多少个结点
if(linkList){
//遍历链表里的每个元素,依次释放(头删法),例如[1,2,3,4,5],先删结点1,变成[2,3,4,5],再删结点2,一直删的是第一个结点
Node *node = &linkList->head;
//node->next保证下一个不空
while(node->next){
//删除用备份思想
Node *tmp = node->next;
node->next = tmp->next;
free(tmp);
cnt++;
}
printf("release %d node!\n",cnt);
free(linkList);//完成后才释放表头
}
}
/*pos[1,2,...linkList->len +1]*/
int insertLinkList(LinkList *linkList, int pos, Element e) {
//判断pos的值即插入位置是否正确
if(pos < 1 || pos > linkList->len +1){
printf("insert pos invalid");
return -1;
}
//找到pos-1的位置,进行插入操作
int cnt = 0;
Node *tmp = &linkList->head;
if(tmp && cnt < (pos-1)){
tmp = tmp->next;
cnt++;
}
//tmp指向了待插入位置的前一个结点
Node *new_node = (Node *)malloc(sizeof(Node));
if(new_node == NULL){
printf("malloc node failed!\n");
return -1;
}
//先处理新结点,再处理老结点
new_node->ele = e;
new_node->next = tmp->next;
tmp->next = new_node;
linkList->len++;
return 0;
}
//打印链表
void showLinkList(LinkList *linkList){
Node *node = linkList->head.next;//指向第一个结点的地址
while(node){
printf("%d\t",node->ele);
node = node->next;
}
printf("\n");
}
int deleteLinkList(LinkList *linkList, Element e) {
//找到e元素的前一个位置
Node *node = &linkList->head;
//node存在(非空)且node的下一个结点的值不等于e,等于e时跳出循环
while(node->next && node->next->ele != e){
node = node->next;
}
if(node->next == NULL){
printf("not find\n");
return -1;
}
//利用删除的备份思想
Node *tmp = node->next;
node->next = tmp->next;
free(tmp);
return 0;
}
main.c
#include "linkList.h"
#include <stdio.h>
int main(){
LinkList *link = creatLinkList();
//判断是否创建成功
if(link == NULL){
return -1;
}
//插入结点
for(int i = 0; i < 5; ++i){ //如果每一个链表都从头上插入,链表的倒序
insertLinkList(link,1,i+100);
}
showLinkList(link);
printf("------------------\n");
deleteLinkList(link,100);
showLinkList(link);
releaseLinkList(link);
return 0;
}
运行结果:
小练习:(答案下一期公布)
1. 给定你一个链表,就地倒序(逆置)
例如
链表:10 -》 20 -》 30 -》40
倒序:40 30 20 10
2. 链表归并
A: 2 5 9 10
B: 1 3 4 40 50
将AB两个链表合并成一个链表C,元素按从小到大的顺序排列