数据结构实现基础

Posted 天“码”行空

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构实现基础相关的知识,希望对你有一定的参考价值。

上一讲学习了一些基本的数据类型像数组,结构。我们还学习了一些指针的基本概念。这一讲,我们主要学习两种数据类型,一种叫联合,也叫共同体。还有一种就是链表。

一.联合(共同体)

共同体同结构体在声明形式和访问方式上有些类似,但是它和结构体是完全不一样的。所谓共同体类型,是指将不同的数据项组成一个整体,它们在内存中占用同一段存储单元。其关键字为union(联合的意思)。

定义形式为:

union 共同体名

  类型名 成员名1;
  类型名 成员名2;
  ......
  类型名 成员名n;
;

由于共同体中各个成员变量在内存中使用同一段存储空间,因此共同体变量的长度等于最长的成员的长度。共同体的访问方式同结构体类似。

下面是一个共同体的例子:

union key

  int k;
  char ch[2];
u;

该共同体变量占用的空间是int类型占用的空间与2字节的最大值,即4个字节。

#include<stdio.h>

int main()

	union key
	
		int k;
		char ch[2];
	u;
	
	u.k = 257;
	printf("%d %d\\n",u.ch[0], u.ch[1]);
	
	return 0;
  

运行此代码后,你会发现u.ch[0]和u.ch[1]的值会随着u.k的值的改变而发生改变。由此也可以说明它们占用同一内存空间。

二.链表

在C语言中,我们已经接触过链表。链表是一种重要的基础数据结构,也是实现复杂数据结构的重要手段。它不同于数组,其不按照线性的顺序存储数据,而是有若干个同一结构类型的“结点”依次串接而成的。且每一个结点里保存着下一个结点的地址(指针)。

优点:不需要提前知道数据的大小,而是实现灵活的内存动态管理。

缺点:失去了数组方便随机存取的优点,且增加了结点的指针域,空间开销较大。

  单向链表(默认为带头结点)

      头结点:数据域为空,指针域指向首元结点(存放数据的第一个结点)

  1.单向链表的结构

首先是一个表头变量head,用来存放链表头结点(带头结点的单链表)的地址,链表中每个结点由数据部分和下一个结点的地址部分组成,即每个结点都包含指向下一个结点的指针。链表的最后一个结点称为表尾,其指针域为NULL(表示空地址)。

存储特点:链表中各个结点在内存中可能是不连续存放的,具体存放位置由系统分配。

定义:通常使用结构的嵌套来定义单向链表结点的数据类型。如:

typedef struct node * PtrToNode;
struct node

   ElementType data;
   PtrToNode next;
;


//或


strucu node 

   ElementType data;
   struct node *next;

我们来看上述代码的第一行:typedef struct node * PtrtoNode;通过我们前面所学,我们知道这是typedef类型定义,相当于给一个类型名起一个别名。在这里为什么要给这个类型取一个这样的别名呢?你想过没有?如果没有,那就按照我的思路想想。首先这是一个指向结构的指针类型,也就是说其保存的是一个结构类型的地址,而我们知道结点的指针域就是保存下一结点的地址,所以看来此类型定义是为了方便定义结点的指针域。那么看到PtrtoNode,顾名思义表示“指向Node的指针”之意。这就是为什么要类型定义的原因,是为了更方便地明白指针域的含义。当然也可以不进行类型定义,对程序运行没有任何影响。

链表是一种动态的数据结构。在进行动态存储分配的操作中,C语言提供了几个常用的函数:malloc(), free()。例如要申请大小为struct node结构的动态内存空间,可由下面语句实现:

struct node * = (struct node *)malloc(sizeof(struct node));

若申请成功,p指向分配内存空间起始地址;若未申请到内存空间,则p的值为NULL

2.单链表的常见操作

(1)插入结点

在单向链表head的某个结点p之后插入一新结点的基本过程:首先找到正确位置p,然后申请新结点t并对t的结点信息赋值,最后将t插在p之后。

将结点t插在结点p之后:

t---->next = p---->next;//顺序不能颠倒
p---->next = t;

如果需要在链表的头上插入一个结点t,基本语句为:

t---->next = head;  //两个语句不能颠倒
head = t;

(2)删除结点

从单向链表head中删除一个结点的基本过程是:首先找到要删除的结点前面的结点p,然后删除p之后的那个结点。基本语句为:

t = p---->next;//把待删除的结点的地址保存在t中
p---->next = t---->next;
free(t);

注意:删除一个结点后必须动态释放该结点的空间,为此上述语句中首先要把待删除的结点保留在t中,最后再释放t。

如果删除的是第一个结点,因为是第一个结点,其地址保存在头指针中,基本语句如下:

t = head;
head = head---->next;
free(t);

(3)单向链表的遍历

对单向链表最常见的处理方式是逐个查看链表中每个结点的数据并进行处理,因此,链表的遍历是非常基础的链表程序设计方法。

单向链表的遍历基本程序:

p = head;
while(p != NULL)

   输出p---->next;
   输出p---->num;
   p = p---->next;

(4)链表的建立

在构建链表时,有两种常见的插入结点的方式:

【1】在链表的头上不断插入新结点。

【2】在链表的尾部不断插入新结点。

如果是后者,一般需要一个临时的结点指针一直指向当前链表的最后一个结点,以便新结点的插入。

双向链表

单向链表的构成使得结点访问要按链的指向进行,某一单元的后继单元可以直接通过链指针(Next指针)找到,而要找到其前驱单元,必须从链头开始寻找。如果结点增加一个指针域指向其前驱结点,将在牺牲空间代价的前提下,减少操作的代价。这种在单向链表的基础上,增加指向前驱单元指针(Previous)的链表叫做双向链表。

下图显示了双向链表:

 

单向链表与双向链表的比较:

双向链表结点的数据类型与单向链表相似,只是多了一个前驱单元指针:

typedef struct DNode * PtrToDNode;
struct DNode

   ElementType Data; //存储结点数据
   PtrToDNode Next;     //指向下一个结点的指针
   PtrToDNode previous;    //指向前一个结点的指针
;

对双向链表的插入,删除,和遍历基本思路与单链表相同,但需要同时考虑前后两个指针

 

双向链表中的特例------双向循环链表

定义:双向链表最后一个单元的Next指针指向链表的第一个单元,而第一个单元的Previous指针指向链表的最后一个单元,这样构成的链表称为双向循环链表。

 

以上是关于数据结构实现基础的主要内容,如果未能解决你的问题,请参考以下文章

数据结构与算法基础 模块七

线性表之双向链表

小程序的基础

小程序的基础

c++实现双向单链表

java基础