链表

线性结构 - 链表

        在存储一大波数的时候,我们通常使用的是数组,但有时候数组显得不够灵活,比如下面这个例子: 
    有一串已经排好序的数2 3 5 8 9 10 18 26 32。现在需要往这串数中插入6使其得到的新序列扔符合从小到大排列。如我们使用数组来实现这一操作,则需要将8和8后面的数都依次往后挪一位,如下:

    这样的操作明显很耽误时间,如果使用链表则会快很多。那什么是链表呢?请看下图:

    如果需要在8前面插入一个6,就只需像下图这样更改一下就可以了,而无需再将8及后面的数依次往后挪一位,是不是很节省时间呢?

    那如何实现链表呢?在C++中可以使用指针和new动态分配内存来实现。下面先复习指针。



*********** 指针 ***********

        先看下面两行语句。
        int a;
        int *p;

    第一行我们熟悉了,就是定义一个整型变量a。第二行你会发现p前面多了一个 * 号,这就表示定义了一个整型变量p。即定义一个指针,只需在变量前面加一个 * 号就OK啦。
    接下来,指针有什么作用呢?答案是:存储一个地址。确切地说是存储一个内存空间得地址,比如说整型变量a的地址。严格地说这里的指针p也只能存储“一个存放整数的内存空间”的地址,因为在定义的时候我们已经限制了这一点(即定义的时候*p的前面是int)。当然你也可以定义一个只能用来存储“一个存放浮点数的内存空间”的地址,例如:
        double *p; 
    简单地说,指针就是用来存储地址的。你可能要问:不就是存储地址嘛,地址不都一样吗,为什么还要分不同类型的指针呢?不要,下面我们一一解释。
    先解决一个问题:整型指针p如何才能存储整型变量a的地址呢?很简单,如下:
        p = &a; 
    &称为取地址符。我们可以形象地理解整型指针p指向了整形变量a。p指向了a之后,有什么用呢?用处就是我们可以用指针p来操作变量a了。 例如我们可以通过操作指针p来输出变量a的值。如下:

#include <iostream>
using namespace std;
int main(){
	int a = 10;
	int *p;   //定义一个指针p
	p = &a;   //指针p获取变量a的地址
	cout << *p << endl; 
	return 0;
}

        运行结果为:10
 

C++语言中*的三个用途:
    1. 乘号,用做乘法运算,例如5*6。
    2. 申明一个指针,在定义指针变量时使用,例如int *p; 。
    3. 间接运算符,取得指针所指向的内存中的值,例如 cout << *p; 。

         回想一下,我们想在程序中存储一个整数10,除了使用int a;这种方式在内存中申请一个区域来存储,还有一种动态存储方法:
        new int; 
    new的作用就是从内存中申请分配指定字节大小的内存空间。由于int整型是4个字节的,所以上面的代码就是从内存中动态分配4个字节的内存空间。

new动态内存分配的基本格式:
     类型名 * 指针 = new 类型名; 如int *p = new int;
delete释放内存空间的基本格式:
    delete 指针名; 如 delete p;

         为什么要用不同类型的指针呢?因为指针变量存储的是一个内存空间得首地址(第一个字节的地址),但是这个空间上多少个字节,用来存储什么类型的数,则是由指针的类型来标明的。这样系统才知道应该取多少个连续内存作为一个数据。
    现在我们可以通过指针p对刚才申请的内存空间进行操作了,比如我们向这个空间存入整数20,完整的代码如下:

#include <iostream>
using namespace std;
int main(){
	int *p = new int;    //定义一个指针p, 并获取动态分配的内存空间地址 
	*p = 20;    //向指针p指向的内存空间中存入20 
	cout << *p << endl;    //输出指针p所指向的内存中的值 
	delete p;  //释放指针p所指向的内存空间 
	return 0;
}



*********** 链表 ***********

        

    下面我们学习链表中的每一个结点应该如何存储。
    每个结点都有两部分组成:左边的部分用来存放具体的数值,那么用一个整形变量就可以;右边的部分需要存储下一个结点的地址,可以用指针来实现(也称为后继指针)。这里我们定义一个结构体类型来存储这个结点。如下:
        struct node{
            int data;
            struct node *next;
        };

         
    上述代码中,我们定义了一个叫做node的结构体类型,这个结构体类型有两个成员。第一个是整型data,用来存储具体的数值;第二个成员是一个指针,用来存储下一个结点的地址。因为下一个结点的类型也是struct node,所以这个指针的类型也必须是struct node * 类型的指针。 
    如何建立链表呢?首先我们需要一个头指针head指向链表的最开始。当链表还没有建立的时候头指针head为空(也可以理解为空结点)。
        struct node *head;
        head = NULL; //头指针head初始为空,注意NULL必须要大写

    下面我们创建第一个结点,并用临时指针p指向这个结点。
        struct node *p;
        p = new struct node; //动态申请一个空间

接下来分别设置新创建的这个结点的左半部分和右半部分。
        cin >> a; 
        p->data = a; //将数据a存储到当前结点的data域中
        p->next = NULL; //设置当前结点的后继指针指向空,也就是当前结点的下一个结点为空 


    上面的代码中我们发现了一个很奇怪的符号“->”。->叫做结构体指针运算符,也是用来访问结构体内部成员的。因为此处p是一个指针,所以不能使用 . 号访问内部成员,而要使用 ->。
    下面来设置头指针并设置新创建结点的*next指向空。头指针的作用是方便以后从头遍历整个链表。
        if (head == NULL); 
            head = p; //若这是第一个创建的结点,则将头指针指向这个结点
        else
            q->next = p; 
//若不是第一个创建的结点,则将上一个结点的后继指针指向当前结点 

    如果这是第一个创建的结点,则将头指针指向这个结点。

    如果不是第一个创建的结点,则将上一个结点的后继指针指向当前结点。

    最后要将指针q也指向当前结点,因为待会儿临时指针p将会指向新创建的结点。
        q = p; //指针q也指向当前结点



【程序1】逆序输出 (laoj1024)

        按照顺序读入n个整数,以逆序方式输出。 
        【样例输入】
               3 
               1 2 3
        【样例输出】
               3 2 1

整个代码实现如下:

#include <iostream>
using namespace std;
struct node{
	int data;
	struct node *next;
};
int main(){
	struct node *head, *p, *q, *t;
	int i, n, a;
	cin >> n;
	head = NULL;
	for(i=1; i<=n; i++){
		cin >> a;
		q=p;
		p = new struct node;
		p->data = a;
		p->next = NULL;
		if(head==NULL)
			head=p;
		else
			p->next=q;
	}
	//逆序输出链表中的所有数 
	while(p->next!=NULL){
		cout << p->data << " ";
		p = p->next;
	}
	cout << p->data << endl;
	return 0;
}

         需要说明的是,上面的代码没有释放(delete)动态申请的空间,虽然没有错误,但是这样会很不安全,同学们可以添加进去。

    如何往链表中插入元素呢?操作如下:

    先用一个临时指针t从链表的头部开始遍历。 
        t = head; //从链表的头部开始遍历

    等到指针t的下一个结点的值比6大的时候,将6插入到中间。即 t->next->data大于6时进行插入,代码如下:

	cin >> a;   //读入待插入的数
	while (t != NULL){   //当没有到达链表尾部的时候循环
		if (t->next->data > a){//如果当前结点下一个结点的值大于待插入数,插入
			p = new stuct node;   //申请一个动态空间,用来存放新增结点
			p->data = a; 
		//新增结点的后继指针指向当前结点的后继指针所指向的结点
			p->next = t->next;   
			t->next = p;   //当前结点的后继指针指向新增结点
			break;   //插入完毕退出循环
		}
		t = t->next; //继续下一个结点
	}
链表中插入一个元素:
#include  <iostream>
using namespace std;
struct node{  //用结构体表示链表的结点类型 
	int data;
	struct node *next;
};
int main(){
	struct node *head, *p, *q, *t;
	int i, n, a;
	cin >> n;
	head = NULL;  //头指针初始为空 
	for(i=1; i<=n; i++){  //循环读入n个数 
		cin >> a;
		//动态申请一个空间,用来存放一个结点,并用p指向这个结点 
		p = new struct node;  
		p->data = a;  //将数据a存入当前结点的data域中 
		p->next = NULL;  //设置当前结点的下一个结点为空 
		if (head ==NULL) head = p; //如果这是第一个结点,则将头指针指向它 
		else q->next = p; //否则将上一个结点的后继结点指向当前结点 
		q=p;   //指针q也指向当前结点 
	}
	cin >> a;  //读入待插入的数
	t = head;  //从链表头部开始遍历 
	while (t!=NULL){  //当没有到达链表尾部的时候循环 
		if(t->next->data > a){  //如果当前结点的值大于插入数,插入 
			p = new struct node;
			p->data = a;
			//新增结点的后继指针指向当前结点的后继指针所指向的结点
			p->next = t->next;  
			t->next = p; //当前结点的后继指针指向新增结点 
			break; //插入完毕退出循环 
		}
		t = t->next; //继续下一个结点 
	}
	//输出链表中所有数
	t=head;
	while(t!=NULL){
		cout << t->data << " ";
		t = t->next;  //继续下一个结点 
	}
	return 0; 
}

        
可以输入以下数据验证:
9
2 3 5 8 9 10 18 26 32
6

运行结果为:
2 3 5 6 8 9 10 18 26 32

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值