单链表的一般操作

单链表:节点只有一个指针域的链表,称为单链表或线性链表。
特点:
①结点位置任意,逻辑与物理无关

②访问时只能通过头指针进入链表,并通过每一个结点的指针域依次向后扫描其余节点,所以寻找第一个结点和最后一个时间不等、

一、容易混淆的概念:
头指针:是指向链表中第一个结点的指针(可带可不带)

首元结点:是指链表(线性表)中存储的第一个数据元素a1的结点

头结点:是在链表的首元结点之前附设的一个结点
在这里插入图片描述
空表:无头结点时,头指针为空时表示空表;有头结点时,当头结点的指针域为空表示空表

头结点的好处:首元结点地址在头结点指针域中,故处理链表的第一位置和其它位置一致,无需特殊处理;便于统一处理空表和非空表

头结点的数据域:头结点数据域可以为空,也可以存放表长等附加信息,但是此结点不可以计入链表长度值

二、定义与表示:
单链表是由头指针唯一确定,故单链表可以用头指针名字命名,若头指针名为L,则把链表称表L

typedef struct _Node{
	int data;
	struct _Node *next;
}node, *linklist;
//结点类型为—Node
//指向结构体—Node的指针类型叫linklist

下一次定义结点直接 _Node L;

定义指向结点指针可以_Node *p;也可以linklist p;

例如:存储学生学号姓名成绩的单链表结点类型如下

typedef struct student {
  char num[8];      //数据域
  char name[8];      //数据域
  int score;        //数据域
  struct student *next;  //指针域
} Lnode, *LinkList;

在这里插入图片描述
为了统一操作,我们通常这样来定义:

typedef struct {
  char num[8];
  char name[8];
  int score;
} ElemType;

三、基本操作:

#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;
#define OK 1;
#define ERROR -1;
算法1:单链表的初始化(构造一个空表)
 
步骤:生成新节点作为头结点,用头指针L指向头结点;将头结点的指针域置空;
 
Status InitList_L(LinkList &L) {//若是c语言则为linklist *L;
  L = new LNode;  
       //或者 L = (LinkList)malloc(sizeof(LNode));     LNode 是上面定义出来的结点类型
  L -> next = NULL;  
      //指针指向的结构的成员变量用->
  return OK;
}
 
 
 
算法2:判断链表是否为空:链表为空,相当于头结点的指针域为空
 
bool ListEmpty(LinkList L) {
  if (L -> next) return 0//非空返回0
  else return 1}


算法3:单链表的销毁,链表销毁后就不存在了.思路是从头指针开始,依次释放所有结点
p用来删除先前new出来的结点空间,L则是不断向前移动,直到移动到最后空
Status DestroyList_L(LinkList &L) {
  Lnode *p;//或linklist p;
  while (L) {
    p = L;
    L = L->next;            //这一步很重要!!!最后指向空时,p指向最后一个结点,刚好释放所有空间
    delete p;//若是c语言则为free(p);
  }
  return OK;
}
 
 算法4:清空链表
链表仍然存在,但是链表中没有元素,成为空链表了(头指针,头结点仍然存在)

第一步:
如果没有头结点:p = L; 此时p指向首元结点
如果有头结点:p = L->next;此时p通过L指向的头结点的指针域指向首元结点

第二步:
用指针变量q记录下一个结点地址,一边p删除后找不到下一节点
反复执行:p = q; q = q->next; 注意这两部不可以交换,以面地址丢失
结束条件:p == NULL; 循环条件:p != NULL;
 
Status ClearList(LinkList &L) {   //将L重置为空表
  LinkList *p, *q;          
  p = L->next;          //将p指向首元结点
  while (p) {           //p没有到表尾,p非空意味着还有结点还可以删,p空了就说明结点删完了
    q = p->next;        //q用来记录下一个结点地址,以面下一节点地址丢失
    delete p;         //安心删除p所指的空间
    p = q;           //p跟上q
  }
  L->next = NULL;        //将头结点指向空,此时为空表
  return OK;  				//返回成功
}

算法5:求单链表表长
 
从首元结点开始依次计数所有结点
int ListLength_L(LinkList L) {  //返回L中数据元素个数
	LinkList p = L->next;	//p指向首元结点
	int i = 0;
	while (p) {	//p非空结点就计数
		i++;
		p = p->next;
	}
	return i;
}


算法6:取值——取单链表当中第i个元素
 
从链表头指针出发,顺着链表出发,知道搜索到第i个节点位置,链表不是随机存取结构
Status GetElem_L(LinkList L, int i, ElemType &e) {	//获取线性表L中的某个数据元素的内容,通过变量e返回
	p = L->next; j = 1;	//初始化,p指向首元结点 
    while (p && j<i) {	//向后扫描,直到p指向第i个元素p为空
        p = p->next; ++j;
    }
    if (!p || j>i) return ERROR;	//第i个元素不存在
    e = p->data;				//取第i个元素
    return OK;
} 
+补充:一种比较投机取巧的写法(也正确)
int get_node(linklist L, int i){
	linklist p;
	p = L->next;
	int cnt = 1;
	while(p){
		if (cnt != i){
			p = p->next;
			cnt ++;
		}
		else return p->data;
	}
	return ERROR;
}
 
算法7:按值查找(因线性链表只能顺序存取,即在查找时要从头指针找起,时间复杂度为O(n).)

查找 1:返回地址 
linklist check_node1(linklist L, int key){
	linklist p;
	p = L->next;
	int cnt = 1;
	while(p->data != key && p){
		p = p->next;
		cnt ++;
	}
	if(!p)return 0;//
	else return p;//这两行为了统一化书写也可以直接写成 return p;
} 

查找 2:返回是第几个元素 
int check_node2(linklist L, int key){
	linklist p;
	p = L->next;
	int cnt = 1;
	while(p->data != key && p){
		p = p->next;
		cnt ++;
	}
	if(!p)return 0;
	else return cnt;
}


算法8:插入(在第i个元素前插入数据元素e)
1、首先找到a_{i-1}的存储位置p
2、生成一个数据域为e的新节点s,插入新节点
3、新节点指针域指向ai,结点a_{i-1}的指针域指向新节点

//在L第i个元素之前插入数据元素 e,只能在第i个元素之前,而不能在之后,这意味着,要在链表尾部加元素是不能通过该方法解决的。
Status ListInsert_L(LinkList &L, int i, ElemType e) {
//用引用的方式传入参数,可以修改实参
    linklist p = L; int j = 0;		//从位置为0就可以插入了,也就是头结点,而非首元结点
    while (p && j<i-1) { //寻找第i-1个结点,p指向i-1结点
        p = p->next; 
        ++j;
    }
    if (!p || j>i-1) return ERROR; //i大于表长+1或者小于1,插入位置非法
    s = new Lnode; s->data = e; //生成新节点s,将节点s的数据域置为e
    s->next = p->next;		   
    p->next = s;				  //这两步的顺序不能颠倒
    return OK;
}


算法9:删除(第i个元素)
1、首先找到a_{i-1}的存储位置p,保存要删除ai的值
2、令p->指向a_{i+1},p->next=p->next->next //指向后面的后面
3、释放结点ai的空间


Status ListDelete_L(LinkList &L, int i, ElemType &e) {
    p = L; j = 0;	//最开始指向头结点,也就是首元结点之前
    while (p->next && j<i-1) {
        p = p->next; ++j;
    } //寻找第i个结点就是从首元结点开始向后走i-1次,并令p指向要找结点的前驱
    if (!(p->next) || j > i-1) return ERROR; 
    //删除位置不合法p指向结点的指针域是空表示指向表尾,j>i-1表示所找的前驱位置小于零;这两种情况一种是所找i结点小于等于就0,一种是找的i结点位置大于表尾元素位置,都是越界查找了,因此返回ERROR
    q = p->next;  //找到目标节点的前驱保存目标节点
    p->next = q->next; //让前驱结点直接指向后继节点
    e = q->data; //保存删除节点的数据域,也可以不用保存,万一删除结点是有用的,看情况操作 
    delete q; //释放删除节点的空间
    return OK;	//返回工作状态成功
}	


算法10:头插法建立单链表(时间复杂度是O(n)1、从一个空表开始,重复读入数据;
2、生成新节点,将读入数据存放在新节点数据域中
3、从最后一个结点开始,依次将各个结点插入到链表前端

void CreateList_H(LinkList &L, int n) {//n为新增的结点个数
    L=new Lnode; 
	L->next=NULL; //创建头结点,并将指针域置空
	for (i=n; i>0; --i) {
        LinkList p= new LNode, //生成新结点p
		cin>>p=>data; //入元素值 scanf(&p->data); 
		p->next=L>next; //把原来头结点之后的所有数据全部插到新结点p的后面 
		L->next=p; 
    }
} 
 

算法11:尾插法建立单链表(时间复杂度为O(n)1.从一个空表L开始,将新结点逐个插入到链表的尾部,尾指针r指向链表的尾结点。
2.初始时,r同L均指向头结点。每读入一个数据元素则申请一个新结点,将新节点插入尾结点后,r指向新节点。

//正位序输入n个元素的值,建立带头结点的单链表L
 
 
void CreateList_R(LinkList &L, int n){ 
	L=new LNode; L->next=NULL; 
	LinkList r, p;
	r = L;//尾指针r指向头结点 
	for(i=0; i<n; i++) {
    	p= new LNode; cin>>p->data;//生成新结点,输入元素值 
		p->next=NULL;
		r->next=p; //新结点插入到表尾 
		r=p;//r指向新的尾结点 
	} 
} 
 

注: 在插入和删除操作中,
因线性链表不需要移动元素,只要修改指针,一般时间复杂度为O(1)
//(特指只移动指针域,不包含查找前驱结点的过程)
但是,若要在单链表中进行前插或删除操作,由于要从头查找前驱结点,时间复杂度为O(n)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值