单链表

314 阅读9分钟

链式储存结构

结点在存储器中的位置时任意的,即逻辑上相邻的数据元素在物理上不一定相邻,所以链式又被称为非顺序映像链式映像

用一组物理位置任意的存储单元来存放线性表的数据元素。这组存储单元可以时连续的,也可以时不连续的,甚至时零散分布在内存的任意位置上。即链表中元素的逻辑次序和物理次序不一定相同。

单链表由头指针唯一确定,因此单链表可以用头指针的名字命名

  1. 结点:数据元素的储存映像。由数据域和指针域两部分组成。
  2. n个结点由指针链组成一个链表。

线性表的链式表现

  • 结点只有一个指针域的链表,称为但来年表或线性来年表(就是本文的主角

结点由两个指针域的链表,称为双链表;首位相接的链表称为循环链表。以后再说。

image.png 头指针: 是指向链表中第一个结点的指针 首元结点: 是指链表中存储第一个数据元素a1的结点 头节点: 是在来年表的首元结点之前附设的一个结点(头结点可以储存链表长度或其他信息,方便链表操作。也可以没有头结点)

头结点

关于头结点,我们来看看头结点的好处:

  1. 便于首元结点的处理

    首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上的操作和其他位置一致,无需特殊操作。(首元结点不再由头指针指向
  2. 便于空表和非空表的用以处理

    无论链表是否为空表,头指针都是指向头结点的非空指针,因此空表和非空表处理统一。

而你知道头结点也有自己的数据域,其可以为空,也可以放线性表长度等附加信息,但此结点不计入链表长度值 另外无头结点时,头指针为空表示空表。有头结点时,当头结点的指针域为空时表示空表。[头指针指向头节点,即存储头街的的地址] 。

链表(链式储存)的特点

  • 就是前面说的,结点在储存器中的位置是任意的,。即链表中元素的逻辑次序和物理次序不一定相同
  • 访问时只能通过头指针进入链表,并通过每个结点的指针域依次炒向后顺序扫描其余结点(顺序存取法)。

单链表的定义和表示

一张图表示: image.png

存储结构

定义结构体

//将结构类型重命名为node,*LinkList
typedef struct node{//声明结点和指向结点的指针的类型
        ElemType data;//数据域
        struct node *next;//指针域
        }node,*LinkList;//LinkList是指向结构体node的指针类型,让书写更简单(node *L / LinkList L)

那么定义是这样的:image.png这样是自己定义链表头指针,当然你也可以在初始化是返回一个头指针(链表)

函数

image.png

Status InitList(LinkList &L/*node *L LinkList L/); bool ListEmpty(LinkList L); bool DestroyList(LinkList &L); bool ClearList(LinkList &L); int ListLength(LinkList L); Status GetElem(LinkList L,int i,ElemType &e); node *LocateElem_one(LinkList L,ElemType e); int LocateElem_two(LinkList L,ElemType e); Status ListInsert(LinkList &L,int i,ElemType e); Status ListDelete_one(LinkList &L,int i,ElemType &e); Status ListDelete_two(LinkList &L,ElemType x,ElemType &e); void CreateListHead(LinkList &L,int n); void CreateListRear(LinkList &L,int n); void ListTravel(LinkList L);

初始化链表(构造一个空的单链表

/*
 * 1.生成新结点作为头结点,用头指针L指向头结点
 * 2.将头结点的的指针域置空
 */
Status InitList(Linklist &L/*node *L*/){
    L = (LinkList) malloc(sizeof(node));
    //L = new node;___c++
    if(L == NULL) { //判断是否有足够的内存空间
        printf("申请内存空间失败\n");
        return false;
    }
    L->next = NULL;
    return true;
}

或者将链表作为返回值

LinkList InitList() {
    LinkList L;
    L = (LinkList)malloc(sizeof(Node));   //申请结点空间
    if(L == NULL) { //判断是否有足够的内存空间
        printf("申请内存空间失败\n");
        exit(0);
    }
    L->next = NULL;                  //将next设置为NULL,初始长度为0的单链表
 	return L;
}

判断链表为空

//判空
bool ListEmpty(LinkList L){//若为空表,则返回1,否则返回0
    if(L->next)
        return 0;
    else
        return 1;
}

销毁链表

//链表的销毁(销毁后链表不存在,头指针头结点都不存在了
//从头指针开始,依次释放所有结点(头结点也被删除
bool DestroyList(LinkList &L){
    LinkList p;//node *p
    while(L){//L非空
        p = L;
        L = L->next;
        free(p);
        //delete p;
    }
    return true;
}

清空链表

//清空链表(链表仍存在,但是无元素,成为空链表
bool ClearList(LinkList &L){
    LinkList p,q;//node *p,*q
    //p:存放当前需要删除的结点 q:用来存放p的下一个结点
    p = L->next;//保护头指针,P现在是首元结点
    while(p){//非空,没到表尾
        q = p->next;
        free(p);//delete p
        p = q;
    }
    L->next = NULL;
    return true;
}

求单链表的表长

//求链表的表长
int ListLength(LinkList L){
    LinkList  p;//node *p
    p = L->next;//p指向首元结点(第一个结点
    int len = 0;
    //遍历链表,统计结点数
    while (p){//非空
        len++;
        p = p->next;
    }
    return len;
}

链表的取值

//取单链表中对饿第i个元素的内容
//从链表头指针开始,顺着链域next逐个结点往下搜索,直至搜索到第i个结点
Status GetElem(LinkList L,int i,ElemType &e) {
    if(i < 1 && i > ListLength(L))
        return false;
    LinkList p = L->next;
    int cnt = 1;
    while (p && cnt < i){//向后扫描,直到p指向第i个元素或者p为空
        p = p->next;
        cnt++;
    }
    if(!p || cnt > i)
        return false;

    e = p->data;
    return true;
}

链表的查找,分为返回地址和序号两种

//返回地址
node *LocateElem_one(LinkList L,ElemType e){
    LinkList p = L->next;//指向首元结点
    while (p && p->data != e)p = p->next;
    //所有结点都看完了还没找到p为空
    return p;//找到返回L中值为e的元素的地址,查找啊hi白返回NULL
}
int LocateElem_two(LinkList L,ElemType e){
    LinkList p = L->next;
    int cnt = 1;
    while(p && p->data != e){
        p = p->next;
        cnt++;
    }
    if(p)//p不为空,表示找到了
        return cnt;
    else
        return false;
}

插入结点

image.png

//插入
/*
 * 1.首先找到Ai-1的储存位置p
 * 2.生成一个数据域为e的新结点S
 * 3.插入新的结点【1.新阶段的指针域指向Ai 2,结点Ai-1的指针域指向相信结点】
 */
//第i个元素前插入元素e
Status ListInsert(LinkList &L,int i,ElemType e){
    LinkList p = L;//头结点
    int cnt = 0;
    //寻找第i-1个位置,p指向i-1
    while(p && cnt < i - 1){
        p = p->next;
        cnt++;
    }
    if(!p || cnt > i - 1)
        return false;//插入位置非法【i大于表长,i小于1

    LinkList s = (LinkList) malloc(sizeof(node));//新的结点
    //s = new node;
    s->data = e;//存入数据
    //插入
    s->next = p->next;
    p->next = s;
}

删除结点

image.png 首先是删除某个序号的结点

//删除结点
/*
 * 1.首先找到Ai-1的储存位置p,保存要删除的a值
 * 2.令p->next指向Ai+1
 * 3.释放Ai的空间
 * 4.e返回被删除的值a
 */
//^删除某个序号的
Status ListDelete_one(LinkList &L,int i,ElemType &e){
    LinkList p = L;//头结点
    int cnt = 0;
    //寻找第i个元素,并令p指向其前驱(p指向i-1
    while(p->next && cnt < i - 1){
        p = p->next;
        cnt++;
    }
    //删除位置不合法
    if(!(p->next) || cnt > i - 1)
            return false;
    LinkList q = p->next;//存第i个结点
    p->next = q->next;//Ai-1的指针域指向Ai+1
    e = q->data;//保存删除的数据域
    free(q);
    //delete q;
    return true;
}

然后还有删除某个具体数据域的结点

//^删除某个数据域的结点


Status ListDelete_two(LinkList &L,ElemType x,ElemType &e){
    LinkList p,pre;//pre为前驱结点,p为查找的结点。
    p = L->next;//首元结点
    while(p && p->data != x){//查找值为x的元素
        pre = p;
        p = p->next;
    }
    pre->next = p->next;//删除操作,将其前驱next指向其后继。
    e = p->data;//被删除的数据域保存
    free(p);
}

建立单链表

这里都默认为经过初始化

头插法(注释掉的是返回头指针的洗发,尾插法一样

/*头插法
 * 1.从一个空链表开始,重复读入数据
 * 2.生成新结点,将读入的数据存放到数据域中
 * 3.从最后一个结点开始,依次将个节点插入到链表的前端
 */
//传入一个头结点(经过初始化的链表),也可以自己new一个(未经过初始化)
void CreateListHead(LinkList &L,int n){//插入n个结点
    //新的头结点
    L= (LinkList) malloc(sizeof(node));//没经过初始化
    // s = new node;
    L->next = NULL;//头结点指针域置空
    for(int i = 0; i < n; i++){
        LinkList p = (LinkList) malloc(sizeof(node));
        //p = new node;
        cin >> p->data;//输入数据域
        //scanf(&p->data);
        //插入到表头
        p->next = L->next;
        L->next = p;
    }
}

/*
 LinkedList CreateLinkedListHead(int n)//头插法{
	Node *L;
	L = (Node *)malloc(sizeof(Node));
	L->next = NULL;
	printf("输入元素:");
	for (int i = 0; i<n; i++)
	{
		Node *p; //要插入的结点
		p = (Node *)malloc(sizeof(Node));
		cin >> p->data;//输入数据域
		p->next = L->next;
		L->next = p;
	}
	return L;
}
 */

尾插法:

//尾插法
/*
 * 1.从一个空表开始,将新结点逐个插入到链表的尾部,尾指针r指向来年表的尾结点
 * 2.初始化时,r与L同指向头结点。没读入一个数据元素则申请一个新的结点,将新结点插入到为节点后,r指向新的结点
 */
void CreateListRear(LinkList &L,int n){
    L= (LinkList) malloc(sizeof(node));//没经过初始化
    L->next = NULL;
    LinkList r = L;//尾指针,先指向头结点
    for(int i = 0; i < n; i++){
        LinkList p = (LinkList) malloc(sizeof(node));
        //插入到表尾
        cin >> p->data;
        p->next = NULL;
        r->next = p;
        r = p;//r指向新的尾结点
    }
}
/*
 LinkedList CreateLinkedListTail(int n)//尾插法
{
	Node *L;
	L = (Node *)malloc(sizeof(Node));
	L->next = NULL;
	Node *r;
	r = L;
	while (n--)
	{
		Node *p;
		p = (Node *)malloc(sizeof(Node));
		cin >> p->data;//输入数据域
		r->next = p;
		r = p;//之后的循环遇到r就是等同于p r->next就是p->next
	}
	r->next = NULL;
	return L;
}

*/

链表操作: 有序表的合并:

void MergeLinkList(LinkList &La,LinkList &Lb,LinkList &Lc){
    LinkList pa = La->next;
    LinkList pb = Lb->next;//首元结点
    LinkList pc = Lc = La;//将La的头结点作为Lc的头结点
    while (pa && pb){//都不为空表
        if(pa->data <= pb->data){
            pc->next = pa;//此时La的元素接到Lc后面
            //后移
            pc = pa;
            pa = pa->next;
        }
        else{
            pc->next = pb;//此时Lb的元素接到Lc后面
            //后移
            pc = pb;
            pb = pb->next;
        }
        
        //        if(pa)
//            pc->next = pa;
//        else
//            pc->next = pb;
        pc->next = pa ? pa : pb;//如果La空l。那么Lb未空,把Lb剩下的接到Lc中
        free(Lb);
        //Lc,La指向同一节点
    }
}