核心设计思想
简单说一下单链表的核心设计思想:
再来说一下单链表的核心操作:
然后,话不多说,直接上代码。
带头文件的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);
}
}
}
好了,祝大家早安午安晚安