数据结构:线性表(链表的实现)

一、顺序存储的优缺点
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,元素按从小到大的顺序排列

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值