最简单的结构:线性表
先进先出的结构:队列
先进后出的结构:栈
线性表
线性表数据结构具有以下特征:
有且只有一个“首元素”
有且只有一个“末元素”
除末元素之外,其余元素均有惟一的后继元素
除首元素之外,其余元素均有惟一的前驱元素
对于线性表,主要可进行以下操作:
添加结点
插入结点
删除结点
查找结点
遍历结点
统计结点数
其中线性表也分为:顺序表 and 链表
顺序表:在计算机内,保存线性表最简单、最自然的方式,就是把表中的元素一个接一个地放进顺序的存储单元,这就是线性表的顺序存储(Sequence Storage)。线性表的顺序存储是指在内存中用一块地址连续的空间依次存放线性表的数据元素,用这种方式存储的线性表叫顺序表(Sequence List),如图所示。顺序表的特点是表中相邻的数据元素在内存中存储位置也相邻。
程序自己实现线性表:
线性表的接口定义:
public interface IListDS<T> { int GetLength(); //求长度 void Clear(); //清空操作 bool IsEmpty(); //判断线性表是否为空 void Add(T item); //附加操作 void Insert(T item, int i); //插入操作 T Delete(int i); //删除操作 T GetElem(int i); //取表元 T this[int index] { get; }//定义一个索引器 获取元素 int Locate(T value); //按值查找 }
顺序表:
class SeqList<T>:IListDS<T> { private T[] data;//用来存储数据 private int count = 0;//表示存了多少个数据 public SeqList(int size) //size就是最大容量 { data = new T[size]; count = 0; } public SeqList() : this(10) //默认构造函数 容量是10 { } /// <summary> /// 取得数据的个数 /// </summary> /// <returns></returns> public int GetLength() { return count; } public void Clear() { count = 0; } public bool isEmpty() { return count == 0; } public void Add(T item) { if (count==data.Length)//当前数组存满 { Console.WriteLine("当前顺序表已存满,不允许再存入"); } else { data[count] = item; count++; } } public void Insert(T item, int index) { for (int i = count-1; i >= index; i--) { data[i + 1] = data[i]; } data[index] = item; count++; } public T Delete(int index) { T temp = data[index]; for (int i = index+1; i < count; i++) { data[i - 1] = data[i]; } count--; return temp; } public T this[int index] { get { return GetEle(index); } } public T GetEle(int index) { if (index>=0 && index<=count-1)//索引存在 { return data[index]; } else { Console.WriteLine("索引不存在"); return default(T);//返回一个T类型的默认值 } } public int Locate(T value) { for (int i = 0; i < count; i++) { if (data[i].Equals(value)) { return i; } } return -1; } }
操作顺序表:
SeqList<string> seqList=new SeqList<string>(); //使用自己写的顺序表 seqList.Add("123"); //往表中添加元素 seqList.Add("456"); seqList.Add("789"); Console.WriteLine(seqList.GetEle(0));//取表元 Console.WriteLine(seqList[0]);//自身 0号 位置元素 seqList.Insert("777",1);//在 1号 位置插入 for (int i = 0; i < seqList.GetLength(); i++) { Console.Write(seqList[i]+" "); } Console.WriteLine(); seqList.Delete(0);//删除 0号 位置元素 for (int i = 0; i < seqList.GetLength(); i++) { Console.Write(seqList[i] + " "); } Console.WriteLine(); seqList.Clear();//清空该表 Console.WriteLine(seqList.GetLength()); Console.ReadKey();
结果:
链表:顺序表是用地址连续的存储单元顺序存储线性表中的各个数据元素,逻辑上相邻的数据元素在物理位置上也相邻。因此,在顺序表中查找任何一个位置上的数据元素非常方便,这是顺序存储的优点。但是,在对顺序表进行插入和删除时,需要通过移动数据元素来实现,影响了运行效率。线性表的另外一种存储结构——链式存储(Linked Storage),这样的线性表叫链表(Linked List)。链表不要求逻辑上相邻的数据元素在物理存储位置上也相邻,因此,在对链表进行插入和删除时不需要移动数据元素,但同时也失去了顺序表可随机存储的优点。
链表分为:单链表 双向链表 循环链表
单链表的存储:
链表是用一组任意的存储单元来存储线性表中的数据元素(这组存储单元可以是连续的,也可以是不连续的)。那么,怎么表示两个数据元素逻辑上的相邻关系呢?即如何表示数据元素之间的线性关系呢?为此,在存储数据元素时,除了存储数据元素本身的信息外,还要存储与它相邻的数据元素的存储地址信息。这两部分信息组成该数据元素的存储映像(Image),称为结点(Node)。把存储据元素本身信息的域叫结点的数据域(Data Domain),把存储与它相邻的数据元素的存储地址信息的域叫结点的引用域(Reference Domain)。因此,线性表通过每个结点的引用域形成了一根“链条”,这就是“链表”名称的由来。
如果结点的引用域只存储该结点直接后继结点的存储地址,则该链表叫单链表(Singly Linked List)。把该引用域叫 next。单链表结点的结构如图所示,图中 data 表示结点的数据域。
链式存储结构:
下图是线性表(a1,a2,a3,a4,a5,a6)对应的链式存储结构示意图
另外一种表示形式:
单链表节点定义:
class Node<T> { private T data;//数据域 存储数据 private Node<T> next;//引用域 指针 用来指向下一个元素 //构造器 public Node() { data = default(T); next = null; } //构造器 public Node(T value) { data = value; next = null; } //构造器 public Node(T value, Node<T> next) { this.data = value; this.next = next; } //构造器 public Node(Node<T> next) { this.next = next; } //数据域属性 public T Data { get { return data; } set { data = value; } } //引用域属性 public Node<T> Next { get { return next; } set { next = value; } } }
单链表实现:
class LinkList<T>:IListDS<T> { private Node<T> head;//存储一个头结点 //构造器 public LinkList() { head = null; } //单链表的长度 public int GetLength() { if (head == null) return 0; Node<T> temp = head; int count = 1; while (true) { if (temp.Next!=null) { count++; temp = temp.Next; } else { break; } } return count; } //清空单链表 public void Clear() { head = null; } //判断单链表是否为空 public bool isEmpty() { return head == null; } //在单链表的末尾添加新元素 public void Add(T item) { Node<T> newNode = new Node<T>(item);//根据新的数据创建一个新的节点 //如果头结点为空,那么这个新的节点就是头结点 if (head==null) { head = newNode; } else {//把新来的节点放到链表的尾部 //要访问到链表的尾节点 Node<T> temp = head; while (true) { if (temp.Next != null) { temp = temp.Next; } else { break; } } temp.Next = newNode; } } //在单链表的第index个结点的位置前插入一个值为item的结点 public void Insert(T item, int index) { Node<T> newNode = new Node<T>(item); if (index == 0)//插入到头结点 { newNode.Next = head; head = newNode; } else { Node<T> temp = head; for (int i = 1; i <=index-1; i++) { //让temp向后移动一个位置 temp = temp.Next; } Node<T> preNode = temp; Node<T> currentNode = temp.Next; preNode.Next = newNode; newNode.Next = currentNode; } } //删除单链表的第i个结点 public T Delete(int index) { T data = default(T); if (index==0) { data = head.Data; head = head.Next; } else { Node<T> temp = head; for (int i = 1; i <= index - 1; i++) { //让temp向后移动一个位置 temp = temp.Next; } Node<T> preNode = temp; Node<T> currentNode = temp.Next; data = currentNode.Data; Node<T> nextNode = temp.Next.Next; preNode.Next = nextNode; } return data; } public T this[int index] { get { Node<T> temp = head; for (int i = 1; i <= index; i++) { //让temp向后移动一个位置 temp = temp.Next; } return temp.Data; } } //获得单链表的第i个数据元素 public T GetEle(int index) { return this[index]; } //在单链表中查找值为value的结点 public int Locate(T value) { Node<T> temp = head; if (temp==null) { return -1; } else { int index = 0; while (true) { if (temp.Data.Equals(value)) { return index; } else { if (temp.Next != null) { temp = temp.Next; } else { break; } } } return -1; } } }
操作单链表:
LinkList<string> seqList=new LinkList<string>(); seqList.Add("123"); seqList.Add("456"); seqList.Add("789"); Console.WriteLine(seqList.GetEle(0));//操作结果一 Console.WriteLine(seqList[0]);//操作结果二 seqList.Insert("777",1); for (int i = 0; i < seqList.GetLength(); i++) { Console.Write(seqList[i] + " ");//操作结果三 } Console.WriteLine(); seqList.Delete(0); for (int i = 0; i < seqList.GetLength(); i++) { Console.Write(seqList[i] + " ");//操作结果四 } Console.WriteLine(); Console.WriteLine(seqList.Locate("789"));//操作结果五 seqList.Clear(); Console.WriteLine(seqList.GetLength());//操作结果六 Console.ReadKey();
结果:
再来简单了解一下其他两种链表
双向链表
前面介绍的单链表允许从一个结点直接访问它的后继结点,所以, 找直接后继结点的时间复杂度是 O(1)。但是,要找某个结点的直接前驱结点,只能从表的头引用开始遍历各结点。如果某个结点的 Next 等于该结点,那么,这个结点就是该结点的直接前驱结点。也就是说,找直接前驱结点的时间复杂度是 O(n), n是单链表的长度。当然,我们也可以在结点的引用域中保存直接前驱结点的地址而不是直接后继结点的地址。这样,找直接前驱结点的时间复杂度只有 O(1),但找直接后继结点的时间复杂度是 O(n)。如果希望找直接前驱结点和直接后继结点的时间复杂度都是 O(1),那么,需要在结点中设两个引用域,一个保存直接前驱结点的地址,叫 prev,一个直接后继结点的地址,叫 next,这样的链表就是双向链表(Doubly Linked List)。双向链表的结点结构示意图如图所示。
双向链表插入示意图
循环链表
有些应用不需要链表中有明显的头尾结点。在这种情况下,可能需要方便地从最后一个结点访问到第一个结点。此时,最后一个结点的引用域不是空引用,而是保存的第一个结点的地址(如果该链表带结点,则保存的是头结点的地址),也就是头引用的值。带头结点的循环链表(Circular Linked List)如图所示。
我们可以根据自己的实际需要来选择相应的链表。