单链表的设计

核心设计思想 

简单说一下单链表的核心设计思想:

再来说一下单链表的核心操作:

 然后,话不多说,直接上代码。

带头文件的c语言实现 

linklist.h

#ifndef _LINKLIST_H_
#define _LINKLIST_H_


//数据节点
typedef struct _link_list_node link_list_node;

struct  _link_list_node {
        link_list_node *next;
        int value;
};


link_list_node* create();

int get_len(link_list_node *p_head);

void destroy(link_list_node *p_head);

void clear(link_list_node *p_head);

int insert_elem(link_list_node *p_head,int value,int pos);

link_list_node *get_elem(link_list_node *p_head,int pos);

link_list_node *delete_elem(link_list_node *p_head,int pos);

//链表反转
void rev_link_list(link_list_node *p_head);
//得到最后一个链表节点
link_list_node *get_last_node(link_list_node *p_head);


void recu_pnt(link_list_node *p_head);

//冒泡排序
void bubble_sort(link_list_node *p_head);

//快速排序
void quick_sort(link_list_node *p_start,link_list_node *p_behind);

#endif

linklist.c

#include <stdio.h>
#include <stdlib.h>
#include "linklist.h"
//把节点值当成节点头来处理,数据类型一样
//数据类型不一样的指针注意数据类型转换

link_list_node* create()
{
	link_list_node *p_head = (link_list_node*)malloc(sizeof(link_list_node));
	if(p_head != NULL) {
		p_head->next = NULL;
		p_head->value = 0;
	}
	return p_head;
}

int get_len(link_list_node *p_head)
{
	int res = -1;
	if(p_head != NULL) {
		res = p_head->value;
	}
	return res;
}

void destroy(link_list_node *p_head)
{
	if(p_head == NULL) {
		return;
	} else {
		destroy(p_head->next);
		free(p_head);
	}
}

void clear(link_list_node *p_head)
{
	if(p_head != NULL) {
		p_head->value = 0;
	}	
}


int insert_elem(link_list_node *p_head,int value,int pos) 
{
	int res = p_head != NULL && pos >= 0 && pos <= get_len(p_head);
	if(res) {
		link_list_node *node = (link_list_node*)malloc(sizeof(link_list_node));
		res = res && node != NULL;
		if(res) {
			link_list_node *p_cur = p_head;
			//在某个位置插入,数据移动
			int i;
			for(i = 0;i < pos;i++) {
				p_cur = p_cur->next;
			}
			//指向的是合适的位置
			node->value = value;
			node->next = p_cur->next;
			p_cur->next = node;
			p_head->value++;
		}
	} 
	return res;
}
//得到元素
link_list_node *get_elem(link_list_node *p_head,int pos) 
{
	link_list_node *res = NULL;
	if(p_head != NULL && pos >= 0 && pos < get_len(p_head)) {
		link_list_node *p_cur = p_head;
		int i = 0;
		for(;i < pos;i++) {
			p_cur = p_cur->next;
		}
		//当前指针下一个指针
		res = p_cur->next;
	}
	return res;
}

link_list_node *delete_elem(link_list_node *p_head,int pos) 
{	
	link_list_node *res = get_elem(p_head,pos);
	if(p_head == NULL && pos >= 0 && pos < get_len(p_head) && res != NULL) {
		link_list_node *p_cur = p_head;
		int i = 0;
		//在这里pos就已经限制了它走不到最后一个节点去
		while(i < pos && p_cur->next != NULL) {
			p_cur = p_cur->next;
		}
		link_list_node *p_del = p_cur->next;
		p_cur->next = p_del->next;
		p_head->value--;
	}
	return res; 	
}

void recu_pnt(link_list_node *p_head) 
{
	//递归打印
	if(p_head == NULL) {
		return;
	} else {
		printf("%p %d %p\n",p_head,p_head->value,p_head->next);
		recu_pnt(p_head->next);//指针不停的轮替
	}
}

//反转打印
void rev_link_list(link_list_node *p_head) 
{
	if(p_head != NULL) {
		link_list_node *p_pre;
		link_list_node *p_cur;
		link_list_node *p_next;
		//cur指向第一个实际节点
		p_cur = p_head->next;
		p_pre = p_head;//最早的前一个节点,先指向头结点
		while(p_cur != NULL) {
			p_next = p_cur->next;
			p_cur->next = p_pre;
			p_pre = p_cur;
			p_cur = p_next;
		}
		p_head->next = p_cur;
	}
}

//得到最后一个节点链表
link_list_node *get_last_node(link_list_node *p_head)
{
	link_list_node *p_cur = p_head;
	link_list_node *res = NULL;
	if(p_cur != NULL) {
		while(p_cur->next != NULL) {
			p_cur = p_cur->next;
		}
		res = p_cur;
	}
	return res;
}

//数据交换函数
void swap(link_list_node *p1,link_list_node *p2)
{
	int temp = p1->value;
	p1->value = p2->value;
	p2->value = temp;
}


void bubble_sort(link_list_node *p_head)
{
	link_list_node *p1;
	link_list_node *p2;
	for(p1=p_head->next;p1->next != NULL;p1=p1->next) {
		for(p2=p_head->next;p2->next != NULL;p2=p2->next){
			if(p2->value > p2->next->value) {
				swap(p2,p2->next);
			}
		}
	}
}

//快速排序法
void quick_sort(link_list_node *p_start,link_list_node *p_behind) 
{
	if(p_start != p_behind && p_start != NULL) {
		//下面就是具体的业务逻辑代码
		link_list_node *p1 = p_start;
		link_list_node *p2 = p1->next;
		printf("p1:%p\n",p1);
		printf("p2:%p\n",p2);
		//后面的数据整体大循环
		for(;p2 != NULL;p2=p2->next) {
			if(p2->value < p_start->value) {
				printf("交换一次\n");
				p1=p1->next;
				swap(p2,p1);
			}
		}
		//交换地址
		swap(p1,p_start);

		//下面进入递归
		quick_sort(p_start,p1);
		quick_sort(p1->next,p_behind);
	}
}

main.c

#include <stdio.h>
#include <stdlib.h>
#include "linklist.h"

int main()
{
	link_list_node *p_head = create();
	printf("before:%d\n",get_len(p_head));
	insert_elem(p_head,3,get_len(p_head));		  
	insert_elem(p_head,2,get_len(p_head));	
        insert_elem(p_head,1,get_len(p_head));
	link_list_node *last_node = get_last_node(p_head);
	printf("last_node:%p\n",last_node);
	//打印
	recu_pnt(p_head);
	printf("\n----------------------\n");
	//链表地址反转
	//rev_link_list(p_head);
	//递归打印链表,传入尾节点
	//recu_pnt(last_node);	
	//链表的冒泡排序
	link_list_node *p_start = p_head->next;
	quick_sort(p_start,last_node);
	//bubble_sort(p_head);
	recu_pnt(p_head);
	printf("after:%d\n",get_len(p_head));
	return 0;
}

下面来说一下其中的快速排序问题:

//快速排序法
void quick_sort(link_list_node *p_start,link_list_node *p_behind) 
{
	if(p_start != p_behind && p_start != NULL) {
		//下面就是具体的业务逻辑代码
		link_list_node *p1 = p_start;
		link_list_node *p2 = p1->next;
		printf("p1:%p\n",p1);
		printf("p2:%p\n",p2);
		//后面的数据整体大循环
		for(;p2 != NULL;p2=p2->next) {
			if(p2->value < p_start->value) {
				printf("交换一次\n");
				p1=p1->next;
				swap(p2,p1);
			}
		}
		//交换地址
		swap(p1,p_start);

		//下面进入递归
		quick_sort(p_start,p1);
		quick_sort(p1->next,p_behind);
	}
}

一定要记得在if判断的位置,加上p_start != NULL,原因如下:

当p1指针循环到与p2指针重合的位置,也就是说p1,p2指针都到达了p_behind的位置,p1->next就会指向NULL,那么在传入quick_sort(p1->next,p_behind)进行递归循环的时候,就结束不了,并且会造成内存地址访问异常,从而linux会报出一个段错误。

上面的代码,有些是我用来调试加入的代码段,你们可以自信删除,然后测试

c++代码实现 (详细注释版)

#include <cstdio>
#include <cstdlib>

using namespace std;


typedef int elem_type;

//数据节点
struct node
{
	elem_type data;
	node *p_next;
};


//定义一个头结点
struct t_node 
{
	int length;//链表中的数据长度
	node *p_next;//这里应该是第一个结点的指针	
};


//创建结点
t_node* create() 
{
	t_node *p_head = new t_node;
	if (p_head == NULL) 
	{
		return NULL;
	}
	
	return p_head;//返回头结点
}


//插入
//核心:先把指针移动到插入结点的前一个结点
//然后开始断链操作:1.先接后链 2.在接前链
int insert(t_node *p_head, elem_type data, int pos) 
{
	//我们把链表中实际第一个节点位置当成0来看待
	//对于单链表来说:对内存空间长度没有限制,从某种程度来说内存
	//有多大,单链表就可以开多大
	//所以pos我们可以从理论上让它pos == p_head->length,也就是在内存中多加一个位置
	//这样指针可以移动到最后一个结点
	int res = (p_head != NULL) && (pos >= 0) && (pos <= p_head->length);
	//给节点分配空间
	node *p_node = new node;
	res = (p_node != NULL) && res;
	if (res)
	{
		//安全检查没有问题,给结点赋值
		p_node->data = data;
		//定义一个移动指针
		//指针从头结点开始时移动
		node *p_cur = (node *)p_head;//指向了p_head指向的空间
		//这里pos已经限定了不会指针不会循环到NULL位置上去
		//假设length = 3,那么pos < 3,pos最大位置只能取2 
		//也就是说它只能移动到最后一个节点的前一个位置
		for (int i = 0; i < pos; i++) 
		{
			p_cur = p_cur->p_next;
		}
		//移动到当前节点的前一个位置
		//先断后链
		p_node->p_next = p_cur->p_next;
		//再断前链
		p_cur->p_next = p_node;
		//链表长度增加
		p_head->length++;
	}
	return res;
}


//遍历这个结点
void traverse(t_node *p_head)
{
	if (p_head != NULL) 
	{
		//这里还是来考虑两种情况
		//第一种:因为头结点与数据结点都是一样的
		//第二种:所以,我们可以考虑直接把头结点变成数据节点来做
		//比如node *p_move = (node*)p_head
		//但是我们不采用这种做法,这种做法让结构不清晰
		//我们先打打印头结点,然后在去遍历出数据结点
		//另外需要注意一点的是,我们可能会考虑是否打印头结点
		//但是链表当中数据已经涉及到打印头结点,我们考虑还是遍历出来
		printf("%d  %d  %d\n", &p_head, p_head->length, p_head->p_next);
		node *p_move = (node*)p_head->p_next;//注意这里给的是一个头结点的下一个结点,头结点已经打印过了
		while (p_move != NULL) 
		{
			//这里打印位置的时候是考虑:当前位置,数据,当前位置保存的下一个位置
			//有一种情况是:你可能会用&p_move来打印当前位置,但是如果我们这样来打印
			//每次打印出来的就是同一个值,因为p_move在栈上面的地址没动
			//想要得到每一个结点的当前地址,直接打印p_move就是了
			//p_move就是保存的当前地址
			printf("%d  %d  %d\n", p_move, p_move->data, p_move->p_next);
			p_move = p_move->p_next;	
		}
	}
}

//下面其实还可以做尾插与头插
//这里我就不写了
//说一下思路:尾插,每次把指针移动到最后一个就行了;头插:指针指向头,在头部进行插入


//得到某一个位置的值
//考虑:返回值还是返回地址:我这里考虑为地址,拿到一个地址我们可以对这片空间
//进行操作,比如修改这结点的位置的值
//核心:移动到要查找结点的前一个位置,然后通过p_next获得修改
node *get(t_node *p_head, int pos) 
{
	node *res = NULL;
	//pos考虑为从第一个结点,按照索引0编号
	//也就是说,它的位置不能大于长度或者等于长度
	if (p_head != NULL && pos >= 0 && pos < p_head->length)
	{
		//指针的移动次数和pos有关
		//并且是从头结点开始移动
		//还是可以考虑为node *p_move = (node*)p_head;
		//我们这里可以直接按照上面这样写,重点是拿到p_head->next指针
		//然后指向某一个地址之后按照node*来进行访问就可以了
		node *p_move = (node*)p_head;
		for (int i = 0; i < pos; i++)
		{
			p_move = p_move->p_next;
		}
		//上面会移动到需要结点的上一个节点
		res = p_move->p_next;
	}
	return res;
}

//核心操作:删除
//核心:还是移动到被删除结点的前一个结点
//操作:1.直接把当前结点的p_next 指向被删除结点的p_next 
//也就是p_cur->p_next = p_cur->p_next->p_next
//返回考虑这个删除的地址node*
//注意这片空间我们需要delete掉,毕竟这片空间是new出来的
node *delete_node(t_node *p_head, int pos)
{
	node *res = get(p_head, pos);
	//上面如果能正常返回,也就是说pos与p_head都没有问题
	//因为已经做了判定
	if (res != NULL)
	{
		node *p_move = (node*)p_head;
		//不用多余设置其他变量了,让pos--就行了
		//循环的核心思想是:控制指针移动次数
		while (pos > 0) 
		{
			p_move = p_move->p_next;
			pos--;
		}
		//开始操作:
		p_move->p_next = p_move->p_next->p_next;
		p_head->length--;
	}
	delete res;
	return res;
}


//下面在来多说一个销毁这片空间
//有些同学会直接delete p_head
//但是这个只是释放了头部节点的内存啊,那么数据结点的内存呢
//所以这里要递归一下,从后面往前面delete
//如果按照正序delete,那么这个结点的p_next我们根本就找不到了
void destory(t_node *p_head)
{
	//为了避免提示空间、指针不匹配的问题,给节点做一个备份
	//这里说一个问题,指针如果不一致,在移动过程中是会报错的
	//node *p_node = (node*)p_head;
	//这里只关注地址,直接拿p_head来操作行了
	if (p_head == NULL) 
	{
		return;//结束整个递归过程,开始返回
	} 
	else 
	{
		//先移动,在释放
		destory((t_node*)p_head->p_next);//t_node *p_head = node *p_node;按照道理来说没有问题
		delete p_head;
	}	
}		
 
int main()
{
	t_node *p_head = create();
	if (p_head == NULL) 
	{
		printf("头结点创建失败\n");
	}
	else 
	{
		printf("头结点创建成功\n");
	}
	printf("插入之前的数据长度:%d\n", p_head->length);
	//直接全部尾插
	insert(p_head, 1, p_head->length);
	insert(p_head, 2, p_head->length);
	insert(p_head, 3, p_head->length);
	traverse(p_head);
	printf("插入之后的数据长度:%d\n", p_head->length);	
	printf("拿到索引为1 的节点地址:%d\n", get(p_head,1));
	node *p_del = delete_node(p_head, 1);
	printf("删除索引为1的结点:%d\n", p_del);
	//再做删除节点的时候,返回这个地址的时候,我们已经把这片空间给释放掉了
	//测试看能不能获取值
	printf("我们删除的数据值是:%d\n", p_del->data);
	traverse(p_head);
	destory(p_head);
	printf("\n-------\n");
//	delete p_head;//上面就是已经销毁了结点,下面不能再次delete,有个方法是你delete之后
//	把这个结点设置为NULL
	return 0;
}

 java代码实现

public class LinkedListExample {
	
	//c++用指针来做
	//我们采用对象的方式来做
	static class Node {
		int data;
		Node next;

		Node(int data) {
			this.data = data;
			this.next = null;//初始化等于NULL
		}	
	}

	//定义链表的结构体与操作
	static class LinkedList {
		int length;
		Node head;

		LinkedList() {
			//初始化
			this.length = 0;
			this.head = null;
		}

		//插入一个结点
		boolean insert(int data, int pos) {
			//位置检查
			if (pos < 0 || pos > length) {
				return false;
			}
			
			//定义一个新节点,完成这个结点的赋值
			Node newNode = new Node(data);//data赋上了值,但是next目前为NULL

			if (pos == 0) {
				//等于0的时候直接头插
				newNode.next = head;//直接指向了NULL
				head = newNode;//头指向饿了新节点,这里也就是说,头一直是指向了第一个节点
			} else {
				//先来备份一个current节点用于移动
				Node current = head;
				for (int i = 0; i < pos - 1; i++) {
					current = current.next;
				}
				//如果不谁在pos == 0限制
				//在初始化的时候new LinkedList这样一个结点,这个结点不会
				//给我们初始化,也就是head == null
				//你无法用一个null去访问任何东西
				//先接后链,在接前链
				newNode.next = current.next;
				current.next = newNode;
			}
			

			length++;//这个域是放在new LinkedList里面对吧
			return true;		
		}

		//遍历整个结点
		void traverse() {
			Node current = head;//指向第一个结点
			while (current != null) {
				System.out.println(current.data);
				//指针往下面移动
				current = current.next;
			}

			System.out.println();
		}

		//下面通过某一个位置来得到一个结点
		//返回这个结点的内存地址
		Node get(int pos) {
			//防止指针越界
			if (pos < 0 || pos > length) {
				return null;
			}

			//定义一个移动指针
			Node current = head;
			for (int i = 0; i < pos; i++) {
				current = current.next;
			}
			
			return current;
		}

		//下面还是核心操作,通过某一个位置删除一个结点
		//核心:还是指向删除节点的前一个结点
		//只不过在java里面需要注意的就是
		//头结点head其实就是保存的第一个节点的位置
		//也就是需要把pos == 0这个位置单独拿出来考虑
		//另外移动的过程也要 < pos - 1
		Node delete(int pos) {
			if (pos < 0 || pos >= length) {
				return null;
			}

			//这个结点用于保存删除节点的地址
			Node deleteNode;
			//pos == 0,其实就是改变一下头结点的指向
			if (pos == 0) {
				deleteNode = head;
				head = head.next;
			} else {
				Node current = head;
				for (int i = 0; i < pos - 1;i++) {
					current = current.next;
				}

				//当前结点对象的下一个结点
				deleteNode = current.next;
				current.next = current.next.next;
			}

			length--;
			return deleteNode;
		}

		//销毁一张链表,这里java的优势一下就出来了
		//自动管理回收内存,不需要我们手动free或者是delete掉一个结点
		void destroy() {
			head = null;
			length = 0;
		} 

	}


	//这里通过main调用,所以上面的类全部都要设置为静态
	public static void main(String[] args) {
		LinkedList linkedList = new LinkedList();
		//上面链表就被创建了
		//
		System.out.println("链表被new出来了");

		System.out.println("插入之前的长度:" + linkedList.length);
		//开始插入数据
		linkedList.insert(1,linkedList.length);
		linkedList.insert(2,linkedList.length);
		linkedList.insert(3,linkedList.length);
		System.out.println("插入之后的长度:" + linkedList.length);
		linkedList.traverse();

		Node res = linkedList.get(2);
		System.out.println("2号索引位置的值是:" + res.data);
		//我们把数值为2的这个结点删除
		Node deleteRes = linkedList.delete(1);
		if (deleteRes != null) {
			System.out.println("删除的节点是:" + deleteRes.data);
		}
	}

}

 好了,祝大家早安午安晚安

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值