C语言实现链表的基本操作(增删改查)

单链表的基本操作,增删改查

概述:
  • 链表属于线性结构,由多个节点构成,与数组不同,他是离散存储,通过指针相连

  • 链表的节点必须包含两个信息“有效数据和指针”,也就是“数据域和指针域 ”

  • 每一个节点都有前驱节点和后续节点,首节点没有前驱节点,尾节点没有后续节点

  • 链表的“首节点”前面还有一个“头节点”,头节点不存放有效数据,数据域是个无效值,头节点作用是方便对链表进行操作

  • 双链表:每一个节点有两个指针域,一个指针域指向上一个节点,一个指针域指向下一个节点

  • 循环链表:能通过任何一个节点找到其他所有节点(也就是最后一个节点指向第一个节点形成了一个循环)

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>

// 链表的节点必须包含两个信息“有效数据和指针”,也就是“数据域和指针域 ”
typedef struct Node {
	//数据域
	int data;
	//指针域
	struct Node *pNext;//链表中的指针,指向的是下一个节点的整体,所以指针类型必须和下一个节点类型相同
} *PNODE, NODE;

PNODE init_list(); // 初始化链表
void add_list(PNODE pHead,int val); // 给链表追加节点
void traverse_list(PNODE pHead); // 遍历输出链表
bool is_empty(PNODE pHead); // 判断链表是否为空
int length_list(PNODE pHead); // 获取链表长度
void sort_list(PNODE pHead); // 对链表排序
// 这里我使用的是下标index(从0开始),注意不是pos(从1开始) 
bool insert_list(PNODE pHead,int index,int val);// 插入节点 
bool delete_list(PNODE pHead,int index,int *val);// 删除节点 

int main() {
	PNODE pHead = init_list();

	int i;
	for(i = 0; i < 5; i++) {
		add_list(pHead, pow(2,i));
	}

	traverse_list(pHead);

	if(is_empty(pHead)) {
		printf("链表为空\n");
	} else {
		printf("链表不为空\n");
	}

	printf("链表长度为:%d\n",length_list(pHead));

	sort_list(pHead);
	traverse_list(pHead);

	insert_list(pHead,0,5);
	printf("插入成功:"); 
	traverse_list(pHead);

	int val;
	delete_list(pHead, 5, &val);
	printf("删除的值为:%d\n", val);
	traverse_list(pHead);
	//pHead指向的内存是我在init_list函数中动态分配,执行完之后需要手动销毁 
	free(pHead);

	return 0;
}

PNODE init_list() {
	PNODE pHead = (PNODE) malloc(sizeof(NODE));
	if(pHead == NULL) {
		printf("内存分配失败\n");
		exit(-1);
	}
	pHead->pNext = NULL;
	return pHead;
}

void add_list(PNODE pHead,int val) {
	// 1.新建一个节点
	PNODE pNew = (PNODE) malloc(sizeof(NODE));
	if(pNew == NULL) {
		printf("内存分配失败\n");
		exit(-1);
	}
	// 2.给新节点赋值
	pNew->data = val;
	pNew->pNext = NULL;
	// 3.将pTail指向尾节点
	PNODE pTail = pHead;
	while(pTail->pNext != NULL) { // 当pTail指向的节点不为NULL,说明pTail指向的不是尾节点
		pTail = pTail->pNext; // 进入循环说明pTail指向的还不是尾节点,所以将pTail指向下一个节点
	}
	// 4.最后将新建节点pNew挂载到尾节点之后
	pTail->pNext = pNew;
}

void traverse_list(PNODE pHead) {
	// 1.创建一个指针(pMOVE),指向首节点(pHead->pNext)
	PNODE pMove = pHead->pNext;
	int i;
	// 2.如果pMove指向的节点不为空,则进入循环输出
	while(pMove != NULL) {
		printf("%d ", pMove->data);
		//3.将 pMove 指向下一个节点
		pMove = pMove->pNext;
	}
	printf("\n");
	return;
}

bool is_empty(PNODE pHead) {
	if(pHead->pNext == NULL) {
		return true;
	} else {
		return false;
	}
}

int length_list(PNODE pHead) {
	PNODE pMove = pHead;
	int len = 0;
	while(pMove->pNext != NULL) {
		pMove = pMove->pNext;
		len++;
	}
	return len;
}

void sort_list(PNODE pHead) {
	//这里使用的是选择排序,与数组的选择排序有点不同,因为链表不能记录最小值的下标,所以每轮比较会进行多次交换
	int i,j,t;
	int len = length_list(pHead);
	PNODE p, q;
	for(i = 0, p = pHead->pNext; i < len-1; i++) {
		for(j = i+1,q = p->pNext; j < len; j++, q = q->pNext) {
			if(p->data > q->data) {
				t = p->data;
				p->data = q->data;
				q->data = t;
			}
		}
	}
	return;
}

bool insert_list(PNODE pHead,int index,int val) {
	int i = 0;
	PNODE p = pHead;
	// 在下标index前插入节点,通过循环拿到下标index前面的节点,这样便于插入
	while(p != NULL && i < index) {
		p = p->pNext;
		i++;
	}
	// 判断p是否指向 NULL,插入的下标位置index是否小于0
	if(p == NULL || index < 0) {//老师这里用的是 i>pos所表达的意思就是 index<0,个人认为index<0更加便于理解
		return false;
	}

	PNODE pNew = (PNODE) malloc(sizeof(NODE));
	if(pNew == NULL) {
		printf("内存分配失败\n");
		exit(-1);
	}
	pNew->pNext = p->pNext;
	pNew->data = val;
	p->pNext = pNew;

	return true;
}

bool delete_list(PNODE pHead,int index,int *val) {
	int i = 0;
	PNODE p = pHead;
	// 这里删除,也是通过循环拿到“需要删除节点的前一个节点 ” 
	while(p->pNext != NULL && i < index) {
		p = p->pNext;
		i++;
	}
	if(NULL == p->pNext || index < 0){
		return false;
	}
	*val = p->pNext->data; 
	// 删除 p 后面第一个节点,并让 p 指向后面的第二个节点 
	PNODE q = p->pNext;
	// 判断后面的第二个节点是否存在,不存在直接赋值为NULL 
	p->pNext = p->pNext->pNext == NULL 	? NULL : p->pNext->pNext;
	free(q);
	q = NULL;
	
	return true;
}
  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值