由于线性表操作相对前面二叉树,排序算法等比较简单,所以,就今天一口气写完它的所有基本概念和操作。本文,首先介绍了一下,线性表有哪几种实现方式及各自的优缺点,然后还以单链表为例,实现了链表的头插法创建、尾插法创建、查找元素、删除元素、增加元素等常用操作,希望看了之后能有所收获!
一、线性表存储方式
1.顺序存储结构
指的是用一段地址连续的存储单元一次存储线性表的数据元素。一般用,一维数组来实现顺序存储结构。
2.链式存储结构
指的是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。这就意味着,这些数据元素可以存在内存未被占用的任意位置。
3.二者的优缺点比较
比较如下图:
二、线性表实现方式
1.顺序存储
用一维数组来描述,增删改查,也都是针对数组进行操作,在这里就不过多说明。
2.单链表
2.1单链表结点定义
为了表示每个数据元素a(i)与其直接后继数据元素a(i+1)之间的逻辑关系,对数据元素a(i)来说,除了存储本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素信息的域成为数据域,把存储直接后继位置的域成为指针域。指针域中存储的信息称为指针或链。这两部分信息组成数据a(i)的存储映像,称为结点(Node)。
2.2单链表定义
n个结点(a(i)的存储映像)链结成一个链表,即为线性表的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。具体实现方式参考第三节。
2.3单链表存储结构
public class Node<T> {
public T data; //数据域
public Node<T> next; //指针域
public Node()
{ }
public Node(T value)
{
this.data = value;
}
}
2.3头指针
头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针;头指针具有标识作用,所以常用头指针冠以链表的名字;无论链表是否为空,头指针均不为空。头指针是链表的必要元素。
2.4头结点
头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义(也可存放链表的长度);有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其它结点的操作就统一了;头结点不一定是链表必须要素。
2.5与顺序存储结构优缺点比较
3.静态链表
个人觉得这是一个非常有意思的存储方式。下面我来看看。
静态链表也是用数组存储的,不同顺序存储的是,静态链表元素是由两个数据域组成,data和cur。也就是说,数组的每个下标都对应一个data和一个cur。数据域data,用来存放数据元素,也就是通常我们要处理的数据;而cur相当于单链表中的next指针,存放该元素的后继在数组中的下标,把cur叫做游标。
这时数组的第一个元素的cur用来存放备用链表第一个节点的下标,数组最后一个元素的cur用来存放第一个插入元素的下标,相当于头结点。具体以下图为例:
“庚”为最后一个有值元素,所以它的cur设置为0。而最后一个元素的cur则因“甲”是第一个有值元素而存有它的下标为1.而第一个元素则因空间空间的第一个元素下标为7,所以它的cur存有7。
3.2静态链表优缺点
优点:在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点。
缺点:没有解决连续存储分配带来的表长难以确定的问题;失去了顺序存储结构随机存取的特性。
4.循环链表
将单链表中终端结点指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表成为单循环链表,简称循环链表。其本质还是单链表。二者主要差异在于循环的判断条件上,原来判断p.next是否为空(p为当前结点),现在是p.next不等于头结点,则循环未结束。注意:循环链表不一定非要头结点。
5.双向链表
双向链表是在单链表的每个节点中,再设置一个指向其前驱结点的指针域。存储结构如下:
public class Node<T> {
public T data; //数据域
public Node<T> next; //后继
public Node<T> prior; //前驱
public Node()
{ }
public Node(T value)
{
this.data = value;
}
}
5.1双向链表插入操作
图来源于:程杰的大话数据结构。
s.prior =p; //把p赋值给s的前驱,如图第一步
s.next = p.next; //把p.next赋值给s的后继,如图第二步
p.next.prior = s; //把s赋值给p.next的前驱,如图第三步
p.next = s; //把s赋值给p的后继,如图第四步
5.2双向链表删除操作
p.prior.next = p.next; //把p.next赋值给p.prior的后继,图中的第一步
p.next.prior = p.prior; //把p.prior赋值给p.next的前驱,图中的第二步
5.3其它操作
由于双向链表是单链表中扩展出来的结构,所以它的很多操作是和单链表相同的,比如求长度、查找元素,获得元素位置等,这些操作只需要一个方向的指针即可,另一个没什么作用。
三、实现单链表及常用操作
链表实现类LinkListTest,其内部结点类Node。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 线性表_链式存储
{
class LinkListTest
{
public int COUNT = 0;//线性表元素的个数
private static LinkListTest _instance = null;
public static LinkListTest Instance
{
get {
if (_instance == null)
_instance = new LinkListTest();
return _instance;
}
}
//内部结点类
public class Node<T> {
public T data; //数据域
public Node<T> next; //指针域
public Node()
{ }
public Node(T value)
{
this.data = value;
}
}
//头插法创建线性表 线性表的数据与输入的数据顺序相反
//类似于插队,始终让新结点在第一的位置
public Node<T> CreateListHead<T>(T[] datas)
{
//创建一个带头结点的单链表
Node<T> headNode = new Node<T>();
headNode.next = null;
COUNT = 0;
for (int i = 0; i < datas.Length; i++)
{
//给headNode链表创建新的结点
Node<T> newNode = new Node<T>(datas[i]);
newNode.next = headNode.next;
headNode.next = newNode;
COUNT++;
}
return headNode;
}
//输出线性表
public void PrintLinkList<T>(Node<T> head)
{
Node<T> tempNode = head.next;
if (head.next == null)
{
Console.WriteLine("当前线性表为空!");
}
while (tempNode!=null)
{
Console.Write(tempNode.data+" ");
tempNode = tempNode.next;
}
Console.WriteLine();
}
//尾插法 按照输入数据的顺序插入到线性表中
public Node<T> CreateNodeTail<T>(T[] datas)
{
//创建一个指向列表尾部结点的单链表
Node<T> headNode = new Node<T>(); //头结点
Node<T> tailNode = headNode; //尾节点
headNode.next = tailNode;
COUNT = 0;
for (int i = 0; i < datas.Length; i++)
{
Node<T> newNode = new Node<T>(datas[i]);
tailNode.next = newNode;
tailNode = newNode;
COUNT++;
}
tailNode.next = null;//遍历时用于确认到了尾部
return headNode;
}
/// <summary>
/// 获取指定i位置的数据
/// </summary>
/// <typeparam name="T">泛型</typeparam>
/// <param name="head">链表头结点</param>
/// <param name="i">链表第i个元素</param>
/// <returns>返回链表第i个元素的数据域</returns>
public T GetElem<T>(Node<T> head, int i)
{
int j = 0;
T value;
Node<T> tempNode = head.next;
//迭代找到该位置
while (tempNode!=null && j < i)
{
tempNode = tempNode.next;
++j;
}
if (tempNode == null || j > i)
return default(T);
//取值操作
value = tempNode.data;
return value;
}
//向单链表的指定位置i,插入指定的元素
public bool InsertElem<T>(Node<T> head, int i,T value)
{
int j=1;
Node<T> tempNode = head.next;
//迭代找到待插入位置
while (tempNode != null && j < i)
{
tempNode = tempNode.next;
++j;
}
if (tempNode == null || j > i)
return false;
//插入新节点操作
Node<T> newNode = new Node<T>(value);
//先牵右手再牵左手
newNode.next = tempNode.next;
tempNode.next = newNode;
COUNT++;
return true;
}
//删除链表的第i个结点,并返回删除的值
public T DeleteElem<T>(Node<T> head,int i)
{
int j = 1;
Node<T> tempNode = head.next,tempNextNode;
while (tempNode != null && j < i)
{
tempNode = tempNode.next;
++j;
}
if (tempNode == null || j > i)
return default(T);
//删除结点操作
tempNextNode = tempNode.next;
tempNode.next = tempNextNode.next;
T deleteElem = tempNextNode.data;
COUNT--;
return deleteElem;
}
//销毁线性表
public bool DeleteLinkList<T>(Node<T> head)
{
bool flag = false;
if (head != null)
{
head.next = null;
flag = true;
}
COUNT = 0;
return flag;
}
}
}
测试类Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 线性表_链式存储
{
class Program
{
static void Main(string[] args)
{
int[] datas = { 50, 10, 90, 30, 70, 40, 80, 60, 20 };
Console.WriteLine("原始数据:");
foreach (int temp in datas)
{
Console.Write(temp + " ");
}
Console.WriteLine();
/*头插法*/
LinkListTest.Node<int> head = new LinkListTest.Node<int>();
head = LinkListTest.Instance.CreateListHead(datas);
Console.WriteLine("线性表头插法:");
LinkListTest.Instance.PrintLinkList(head);
Console.WriteLine();
/*尾插法*/
LinkListTest.Node<int> tail = new LinkListTest.Node<int>();
tail = LinkListTest.Instance.CreateNodeTail(datas);
Console.WriteLine("线性表尾插法:");
LinkListTest.Instance.PrintLinkList(tail);
Console.WriteLine();
/*插入操作*/
Console.WriteLine("线性表第4个位置插入100:");
LinkListTest.Instance.InsertElem(tail,4-1,100);
Console.WriteLine("当前线性表中的元素个数为:" + LinkListTest.Instance.COUNT);
LinkListTest.Instance.PrintLinkList(tail);
Console.WriteLine();
/*删除操作*/
Console.WriteLine("删除线性表第4个位置:");
LinkListTest.Instance.DeleteElem(tail, 4-1);
Console.WriteLine("当前线性表中的元素个数为:" + LinkListTest.Instance.COUNT);
LinkListTest.Instance.PrintLinkList(tail);
Console.WriteLine();
/*删除线性表操作*/
LinkListTest.Instance.DeleteLinkList(tail);
Console.WriteLine("当前线性表中的元素个数为:"+LinkListTest.Instance.COUNT);
LinkListTest.Instance.PrintLinkList(tail);
Console.ReadKey();
}
}
}
实验结果截图说明:
以上就是今天的线性表的总结,主要实现了单链表的常用操作。如何文中有不对的地方还请指出,避免误导他人,谢谢!!