C#数据结构

Array

概念:数组是用于储存多个相同类型数据的有序集合。

特点:

(1)数组存储在连续的内存上。

(2)数组存储相同的数据类型。

(3)可以直接通过下标来访问数组元素。

声明及初始化:

int[] myArray = new int[5];//声明

int[] myArray = {1,2,3,4,5};//声明并初始化
或
int[] myArray = new int[5]{1,2,3,4,5};//声明并初始化

 当创建一个新的数组时将在CLR托管堆中分配一块连续的内存空间,来存储所声明数组长度和数组类型的元素。

由于数组是在连续内存上存储的,所以它的索引速度非常快。访问一个元素的时间是恒定的,与数组的元素数量无关,且赋值与修改元素也很简单。

但是有优点,那么就一定会伴随着缺点。由于数组是连续存储的,所以在两个元素之间插入新元素就变得不方便。而且就像上面的代码所显示的那样,声明一个新的数组时,必须指定其长度,

这就会存在一个潜在的问题,那就是当我们声明的长度过长时,显然会浪费内存,当我们声明长度过短的时候,则面临着溢出的风险。针对这种缺点,下面介绍一下ArrayList。

ArrayList

概念:ArrayList就是动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了动态的增加和减少元素,实现了ICollection和IList接口,灵活的设置数组的大小等好处。

为了解决数组创建时必须指定长度以及只能存放相同类型的缺点而推出的数据结构。ArrayList是System.Collections命名空间下的一部分,所以若要使用则必须引入System.Collections。正如上

文所说,ArrayList解决了数组的一些缺点。

不必在声明ArrayList时指定它的长度,这是由于ArrayList对象的长度是按照其中存储的数据来动态增长与缩减的。

ArrayList可以存储不同类型的元素。这是由于ArrayList会把它的元素都当做Object来处理。因而,加入不同类型的元素是允许的。

ArrayList arrayList = new ArrayList();
arrayList.Add(1);
arrayList.Add("name");
arrayList.Add(10);
arrayList[2] = 100;
arrayList.RemoveAt(0);

由于ArrayList会把所有的类型都当做Object来做处理,所以ArrayList不是类型安全的。因为把不同的类型都当做Object来做处理,很有可能会在使用ArrayList时发生类型不匹配的情况。

如上文所说,数组存储值类型时并未发生装箱,但是ArrayList由于把所有类型都当做了Object,所以不可避免的当插入值类型时会发生装箱操作,在索引取值时会发生拆箱操作。

//装箱,将值类型转成引用类型
int  myInt = 10;  
object myObj = (object)myInt;  

//拆箱,引用类型转换成值类型
object myObj = 50;
int myInt = (int)myObj;

List

显然,从原理上可以看出,装箱时,生成的是全新的引用对象,这会有时间损耗,也就是造成效率降低。那么为了解决ArrayList不安全类型与装箱拆箱的缺点,所以出现了泛型的概念,作为一

种新的数组类型引入。List是工作中经常用到的数组类型。和ArrayList很相似,长度都可以灵活的改变,最大的不同在于在声明List集合时,我们同时需要为其声明List集合内数据的对象类型,

这点又和Array很相似,其实List内部使用了Array来实现。 

List<string> myList = new List<string>();
myList.Add("str01");
myList.Add("str02");
myList[0] = "newStr01";
myList.RemoveAt(1);

这么做最大的好处就是:

(1)确保类型安全;

(2)取消了装箱和拆箱的操作;

(3)融合了Array可以快速访问的优点以及ArrayList长度可以灵活变化的优点。 

Queue(队列)

概念:队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作

的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。

队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能

最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)的线性表。通过使用Enqueue和Dequeue这两个方法来实现对 Queue 的存取。

顺序队列:

建立顺序队列结构必须为其静态分配或动态申请一片连续的存储空间,并设置两个指针进行管理。一个是队头指针front,它指向队头元素;另一个是队尾指针rear,它指向下一个入队元素的存

储位置,如图所示

每次在队尾插入一个元素时,rear增1;每次在队头删除一个元素时,front增1。随着插入和删除操作的进行,队列元素的个数不断变化,队列所占的存储空间也在队列结构所分配的连续空间中

移动。当front=rear时,队列中没有任何元素,称为空队列。当rear增加到指向分配的连续空间之外时,队列无法再插入新元素,但这时往往还有大量可用空间未被占用,这些空间是已经出队

的队列元素曾经占用过得存储单元。

循环队列:

在实际使用队列时,为了使队列空间能重复使用,往往对队列的使用方法稍加改进:无论插入或删除,一旦rear指针增1或front指针增1 时超出了所分配的队列空间,就让它指向这片连续空间

的起始位置。自己则从MaxSize-1增1变到0,可用取余运算rear%MaxSize和front%MaxSize来实现。这实际上是把队列空间想象成一个环形空间,环形空间中的存储单元循环使用,用这种方

法管理的队列也就称为循环队列。除了一些简单应用之外,真正实用的队列是循环队列。

在循环队列中,当队列为空时,有front=rear,而当所有队列空间全占满时,也有front=rear。为了区别这两种情况,规定循环队列最多只能有MaxSize-1个队列元素,当循环队列中只剩下一个

空存储单元时,队列就已经满了。因此,队列判空的条件是front=rear,而队列判满的条件时front=(rear+1)%MaxSize。队空和队满的情况如图:

Stack(栈)

概念:栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、

入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

与Queue相对,当需要使用后进先出顺序(LIFO)的数据结构时,我们就需要用到Stack了。

首先系统或者数据结构栈中数据内容的读取与插入(压入push和 弹出pop)是两回事!压入是增加数据,弹出是删除数据 ,这些操作只能从栈顶即最低地址作为约束的接口界面入手操作 ,但

读取栈中的数据是随便的没有接口约束之说。很多人都误解这个理念从而对栈产生困惑。而系统栈在计算机体系结构中又起到一个跨部件交互的媒介区域的作用 即 cpu 与内存的交流通道 ,

cpu只从系统给我们自己编写的应用程序所规定的栈入口线性地读取执行指令, 用一个形象的词来形容它就是pipeline(管道线、流水线)。cpu内部交互具体参见 EU与BIU的概念介绍。

栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹

出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针

栈是允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一

般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为先进后出表。

栈可以用来在函数调用的时候存储断点,做递归时要用到栈!

以上定义是在经典计算机科学中的解释。

计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。在i386机器中,栈顶由称为esp的寄存器进行定位。压栈的操作使得栈顶的

地址减小,弹出的操作使得栈顶的地址增大。

栈在程序的运行中有着举足轻重的作用。最重要的是栈保存了一个函数调用时所需要的维护信息,这常常称之为堆栈帧或者活动记录堆栈帧一般包含如下几方面的信息:

(1)函数的返回地址和参数

(2) 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。

Dictionary<K,V>

1、要使用Dictionary集合,需要导入C#泛型命名空间
  System.Collections.Generic(程序集:mscorlib)
2、描述
   1)、从一组键(Key)到一组值(Value)的映射,每一个添加项都是由一个值及其相关连的键组成
   2)、任何键都必须是唯一的
   3)、键不能为空引用null,若值为引用类型,则可以为空值
   4)、Key和Value可以是任何类型(string,int,custom class 等)
3、创建及初始化
   Dictionary<int, string> myDictionary = new Dictionary<int, string>();
4、添加元素
   myDictionary.Add("C#",0);
   myDictionary.Add("C++",1);
   myDictionary.Add("C",2);
   myDictionary.Add("VB",2);
5、查找元素By Key
  if(myDictionary.ContainsKey("C#"))
  {
    Console.WriteLine("Key:{0},Value:{1}", "C#", myDictionary["C#"]);
  }
6.遍历元素 By KeyValuePair
  foreach (KeyValuePair<string, int> kvp in myDictionary)
  {
    Console.WriteLine("Key = {0}, Value = {1}",kvp.Key, kvp.Value);
  }
7、仅遍历键 By Keys 属性
  Dictionary<string, int>.KeyCollection keyCol = myDictionary.Keys;
  foreach (string key in keyCol)
  {
    Console.WriteLine("Key = {0}", key);
  }
8、仅遍历值By Valus属性
  Dictionary<string, int>.ValueCollection valueCol = myDictionary.Values;
  foreach (int value in valueCol)
  {
    Console.WriteLine("Value = {0}", value);
  }
9.移除指定的键值By Remove方法
  myDictionary.Remove("C#");
  if (myDictionary.ContainsKey("C#"))
  {
    Console.WriteLine("Key:{0},Value:{1}", "C#", myDictionary["C#"]);
  }
  else
  {
    Console.WriteLine("不存在 Key : C#");
    }
在System.Collections.Generic命名空间中,与ArrayList相对应的泛型集合是List<T>,与HashTable相对应的泛型集合是Dictionary<K,V>,其存储数据的方式与哈希表相似,通过键/值来保存元素,并具有泛型的全部特征,编译时检查类型约束,读取时无须类型转换。

电话本存储的例子中,使用Dictionary<K,V>来存储电话本信息,代码如下:

Dictionary<string,TelNote> ht=new Dictionary<string,TelNote>();

在Dictionary<K,V>声明中,“<string,TelNote>”中的string表示集合中Key的类型,TelNote表示Value的类型,定义Dictionary<K,V>泛型集合中的方法如下:

Dictionary<K,V> students=new Dictionary<K,V>();

其中“K”为占位符,具体定义时用存储键“Key”的数据类型代替,“V”也是占位符,用元素的值“Value”的数据类型代替,这样就在定义该集合时,声明了存储元素的键和值的数据类型,保证了类型的安全性。

Dictionary<K,V>中元素的操作方法与HashTable相似,添加元素,获取元素,删除元素,遍历集合元素的方法基本相同。

Dictionary<K,V>和HashTable的区别
Dictionary<K,V>和HashTable的相同点:添加元素,删除元素,通过键访问值的方法相同。
Dictionary<K,V>和HashTable的不同点:
Dictionary<K,V>对添加的元素具有类型约束,HashTable可添加任意类型的元素。
Dictionary<K,V>不需要装箱、拆箱操作,HashTable添加时装箱,读取时拆箱。

在Dictionary<K,V>集合中,除了通过键获取值的方法外,还有一种TryGetValue(key)方法,可以通过键获取值,该方法返回值为布尔型,如果存在和键相对应的值,则返回true,否则返回false。避免了因获取不到相应值发生的异常。
using System;
using System.Collections.Generic;
class Program
{
   static void Main()
   {
      //创建Dictionary<K,V>,然后添加元素
      Dictionary < string, string > film = new Dictionary < string, string > ();
      film.Add("韦小宝", "鹿鼎记");
      film.Add("陆小凤", "陆小凤传奇");
      film.Add("张无忌", "倚天屠龙记");
      film.Add("杨过", "神雕侠侣");
      film.Add("令狐冲", "笑傲江湖");
      Console.WriteLine("集合现在的元素个数为{0}", film.Count);
      film.Remove("杨过");
      //遍历集合
      Console.WriteLine("武侠电影的主角及电影名");
      Console.WriteLine("/t主角/t电影");
      foreach (KeyValuePair < string, string > kvp in film)
      {
         Console.WriteLine("/t{0}/t{1}", kvp.Key, kvp.Value);
      }
      //检查元素是否存在,如不存在,添加
      if (!film.ContainsKey("段誉"))
      {
         film.Add("段誉", "天龙八部");
      }
      //获取键的集合
      Dictionary < string, string > .KeyCollection keys = film.Keys;
      //遍历键的集合
      Console.WriteLine("受欢迎的武侠片中主角名");
      foreach (string str in keys)
      {
         Console.WriteLine(str);
      }
      Dictionary < string, string > .ValueCollection values = film.Values;
      //遍历值的集合
      Console.WriteLine("最受欢迎的武侠片");
      foreach (string strfilm in values)
      {
         Console.WriteLine(strfilm);
      }
      //遍历元素的另一种方法
      Console.WriteLine("和哈希表相同的遍历元素方法");
      foreach (string strname in film.Values)
      {
         Console.WriteLine(strname);
      }
      //获取键对应的值
      string myfilm = film["令狐冲"];
      Console.WriteLine("主角为令狐冲的电影名{0}", myfilm);
      //获取键对应值的TryGetValue方法
      string objfilm = string.Empty;
      if (film.TryGetValue("段誉", out objfilm))
      {
         Console.WriteLine("主角为段誉的电影是{0}", objfilm);
      }
      else
        Console.WriteLine("没有主角为段誉的电影");
      Console.ReadKey();
   }
}
代码创建了一个Dictionary<K,V>集合,键和值的数据类型是string类型,后边代码的元素添加,删除都和哈希表处理方法相同,遍历元素时不需要进行数据类型强制转换。Dictionary<K,V>通过键取值的TryGetValue方法,此方法包括两个参数,一个是要查询的键,另一个是获取的值,注意值前面使用out关键字。

注意:使用TryGetValue方法时,参数一定要使用out关键字,否则编译失败。

Pointer(指针)

指针,是C语言中的一个重要概念及其特点,也是掌握C语言比较困难的部分。指针也就是内存地址,指针变量是用来存放内存地址的变量,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。

指针是一个占据存储空间的实体在这一段空间起始位置的相对距离值。在C/C++语言中,指针一般被认为是指针变量,指针变量的内容存储的是其指向的对象的首地址,指向的对象可以是变量(指针变量也是变量),数组,函数等占据存储空间的实体。

在计算机中, 所有的数据都是存放在存储器中的, 不同的数据类型占有的内存空间的大小各不相同。内存是以字节为单位的连续编址空间, 每一个字节单元对应着一个唯一的编号, 这个编号被称为内存单元的地址。比如: int类型占4个字节, char类型占1个字节等。内存为变量分配存储空间的首个字节单元的地址, 称之为该变量的地址。地址用来标识每一个存储单元, 方便用户对存储单元中的数据进行正确的访问。在高级语言中地址形象地称为指针。

指针相对于一个内存单元来说,指的是单元的地址,该单元的内容里面存放的是数据。在C语言中,允许用指针变量来存放指针,因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。

指针变量是存放一个变量地址的变量,不同于其他类型变量,它是专门用来存放内存地址的,也称为地址变量。定义指针变量的一般形式为:类型说明符*变量名。

类型说明符表示指针变量所指向变量的数据类型;*表示这是一个指针变量;变量名表示定义的指针变量名,其值是一个地址,例如:char*p1;表示p1是一个指针变量,它的值是某个字符变量的地址。

指针使用注意事项

(1)不允许把一个数赋予指针变量

//错误示范
int a = 100;
int *myPointer;
myPointer = a;

//正确示范
int a = 100;
int *myPointer;
myPointer = &a;

(2) 改变形参不代表改变实参

//错误示范
#include<stdio.h>
Change(int *x,int *y)
{
   int *temp;
   temp = x;
   x = y;
   y = temp;
}

int Main()
{
   int a,b;
   int *ap,*bp;
   scanf("%d,%d",&a,&b);
   ap=&a;
   bp=&b;
   if(a<b) Change(ap,bp);
   printf("\n%d,%d\n",*ap,*bp);
   return 0;
}

//正确示范
#include<stdio.h>
Change(int *x,int *y)
{
   int *temp;
   temp = *x;
   *x = *y;
   *y = temp;
}

int Main()
{
   int a,b;
   int *ap,*bp;
   scanf("%d,%d",&a,&b);
   ap=&a;
   bp=&b;
   if(a<b) Change(ap,bp);
   printf("\n%d,%d\n",a,b);
   return 0;
}

 (3)字符串指针

//错误示范
int Main
{
   char *name = "赵四";
   printf("%c\n",name);
   return 0;
}

//正确示范
int Main
{
   char *name = "赵四";
   printf("%s\n",name);
   return 0;
}

(4) 调用函数指针

#include<stdio.h>
int max(int a,int b)
{
   if(a>b) return a;
   else return b;
}

int main()
{
   int (*pomax)(int,int);
   int x,y,z;
   pomax = max;
   printf("input two numbers:\n");
   sanf("%d,%d",&x,&y);
   z = *pomax(x,y);//此处有错
   printf("maxnum=%d",z);
   return 0;
}

分析:函数指针变量不能进行算术运算,这是与数组指针变量不同的。数组指针变量加减一个整数可使指针移动指向后面或前面的数组元素,而函数指针的移动是毫无意义的。函数调用中“(*指针变量名)”的两边的括号不可少,其中的“*”不应该理解为求值运算,在此处只是一种表示符号。要把“z=*pomax(x,y);”改成“z=(*pomax)(x,y);”。 

指向函数的指针

程序编译后,每个函数都有执行第一条指令的地址即首地址,称为函数指针。函数指针即指向函数的指针变量,要间接调用函数可以使用指针变量来实现。

int (*pf)(int, int);

通过将pf与括号中的“*”强制组合组合在一起,表示定义的pf是一个指针,然后与下面的“()”再次组合,表示的是该指针指向一个函数,括号里表示为int类型的参数,最后与前面的int组合,此处int表示该函数的返回值。因此,pf是指向函数的指针,该函数的返回值为int。函数指针与返回指针的函数的含义大不相同。函数指针本身是一个指向函数的指针。指针函数本身是一个返回值为指针的函数。

#include<stdio.h>
float max(float x,float y);
float min(float x,float y);
int main()
{
   float a=1,b=2,c;
   float (*p)(float x,float y);
   p = max;
   c = (*p)(a,b);//等价于max(a,b);
   printf("\nmax=%f",c);
   
   p = min;
   c = (*p)(a,b);//等价于min(a,b);
   printf("\nmin=%f",c);
   return 0;
}

float max(float x,float y)
{
   return x>y?x:y;
}
float min(float x,float y)
{
   return x<y?x:y;
}
运行结果:
max=2.000000
min=1.000000

float (*p)(float x, float y);定义了一个指向函数的指针变量。首先c=(*p)(a,b);语句:因为指针p储存的是max函数的首地址,(*p)(a,b)就相当于max(a,b),函数返回较大值。其次c=(*p)(a,b);语句:因为指针p储存的是min函数的首地址,(*p)(a,b)也就相当于min(a,b),函数返回较小值。

野指针

指针变量在被创建后, 如果不被赋值, 他的缺省值是随机的 ,它的指向是不明确的, 这样的指针形象地称为“野指针”。野指针是很危险的, 容易造成程序出错, 且程序本身无法判断指针指向是否合法。

指针变量初始化时避免野指针的方法: 可以在指针定义后, 赋值NULL空值。

LinkedList(链表)

和数组最大的不同之处就是在于链表在内存存储的排序上可能是不连续的。这是由于链表是通过上一个元素指向下一个元素来排列的,所以可能不能通过下标来访问。如图

既然链表最大的特点就是存储在内存的空间不一定连续,那么链表相对于数组最大优势和劣势就显而易见了。

(1)向链表中插入或删除节点无需调整结构的容量。因为本身不是连续存储而是靠各对象的指针所决定,所以添加元素和删除元素都要比数组要有优势。

(2)链表适合在需要有序的排序的情境下增加新的元素,这里还拿数组做对比,例如要在数组中间某个位置增加新的元素,则可能需要移动很多元素,而对于链表而言可能只是若干元素的指向

发生变化而已。

(3)有优点就有缺点,由于其在内存空间中不一定是连续排列,所以访问时候无法利用下标,而是必须从头结点开始,逐次遍历下一个节点直到寻找到目标。所以当需要快速访问对象时,数组

无疑更有优势。

综上,链表适合元素数量不固定,需要经常增减节点的情况。

二叉树

概念:二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都

较为简单,因此二叉树显得特别重要。二叉树的特点是每个结点最多只能有两棵子树,且有左右之分。

二叉树是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成,是有序树。当集合为空时,称该二叉树为空二

叉树。在二叉树中,一个元素也称作一个结点。

定义:二叉树(Binary tree)是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分

别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树。

形态:二叉树是递归定义的,其结点有左右子树之分,逻辑上二叉树有五种基本形态:

(1)空二叉树

(2)只有一个根结点的二叉树

(3)只有左子树

(4)只有右子树

(5)完全二叉树

特殊形态:

(1)满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。

(2)完全二叉树:深度为k,有n个结点的二叉树当且仅当其每一个结点都与深度为k的满二叉树中编号从1到n的结点一一对应时,称为完全二叉树。

完全二叉树的特点是叶子结点只可能出现在层序最大的两层上,并且某个结点的左分支下子孙的最大层序与右分支下子孙的最大层序相等或大1。

相关术语:

①结点:包含一个数据元素及若干指向子树分支的信息

②结点的度:一个结点拥有子树的数目称为结点的度 。

③叶子结点:也称为终端结点,没有子树的结点或者度为零的结点。

④分支结点:也称为非终端结点,度不为零的结点称为非终端结点 。

⑤树的度:树中所有结点的度的最大值。

⑥结点的层次:从根结点开始,假设根结点为第1层,根结点的子节点为第2层,依此类推,如果某一个结点位于第L层,则其子节点位于第L+1层 。

⑦树的深度:也称为树的高度,树中所有结点的层次最大值称为树的深度。

⑧有序树:如果树中各棵子树的次序是有先后次序,则称该树为有序树。

⑨无序树:如果树中各棵子树的次序没有先后次序,则称该树为无序树。

⑩森林:由m(m≥0)棵互不相交的树构成一片森林。如果把一棵非空的树的根结点删除,则该树就变成了一片森林,森林中的树由原来根结点的各棵子树构成。

二叉树性质:

二叉树遍历:

遍历是对树的一种最基本的运算,所谓遍历二叉树,就是按一定的规则和顺序走遍二叉树的所有结点,使每一个结点都被访问一次,而且只被访问一次。由于二叉树是非线性结构,因此,树的遍历实质上是将二叉树的各个结点转换成为一个线性序列来表示。

树的遍历是树的一种重要的运算。所谓遍历是指对树中所有结点的信息的访问,即依次对树中每个结点访问一次且仅访问一次。二叉树的3种最重要的遍历方式分别称为前序遍历中序遍历后序遍历。以这3种方式遍历一棵树时,若按访问结点的先后次序将结点排列起来,就可分别得到树中所有结点的前序列表,中序列表和后序列表。相应的结点次序分别称为结点的前序、中序和后序。

线索二叉树:

按照某种遍历方式对二叉树进行遍历,可以把二叉树中所有结点排列为一个线性序列。在该序列中,除第一个结点外,每个结点有且仅有一个直接前驱结点;除最后一个结点外,每个结点有且仅有一个直接后继结点。但是,二叉树中每个结点在这个序列中的直接前驱结点和直接后继结点是什么,二叉树的存储结构中并没有反映出来,只能在对二叉树遍历的动态过程中得到这些信息。为了保留结点在某种遍历序列中直接前驱和直接后继的位置信息,可以利用二叉树的二叉链表存储结构中的那些空指针域来指示。这些指向直接前驱结点和指向直接后继结点的指针被称为线索(thread),加了线索的叉树称为线索二叉树

线索二叉树将为二叉树的遍历提供许多遍历。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
C#是一种面向对象的编程语言,它提供了丰富的数据结构和算法库,用于处理和组织数据。下面是一些常见的C#数据结构和它们的特点: 1. 数组(Array):数组是一种线性数据结构,用于存储相同类型的元素。它具有固定大小,可以通过索引访问元素。C#中的数组可以是一维、二维或多维的。 2. 列表(List):列表是一种动态数组,可以根据需要自动调整大小。它提供了添加、删除、插入和查找元素方法。C#中的List类是泛型的,可以存储任意类型的元素。 3. 链表(LinkedList):链表是一种非连续的数据结构,由节点组成,每个节点包含一个值和指向下一个节点的引用。链表可以高效地插入和删除元素,但访问元素需要遍历整个链表。 4. 栈(Stack):栈是一种后进先出(LIFO)的数据结构,只能在栈顶进行插入和删除操作。C#中的Stack类提供了Push、Pop和Peek等方法。 5. 队列(Queue):队列是一种先进先出(FIFO)的数据结构,只能在队尾插入元素,在队头删除元素。C#中的Queue类提供了Enqueue、Dequeue和Peek等方法。 6. 字典(Dictionary):字典是一种键值对的集合,每个键都唯一对应一个值。C#中的Dictionary类是泛型的,可以存储任意类型的键值对。 7. 集合(Set):集合是一种无序且不包含重复元素的数据结构C#中的HashSet类和SortedSet类分别提供了无序和有序的集合实现。 以上是C#中常见的数据结构,它们在不同场景下有不同的应用。你可以根据具体的需求选择合适的数据结构来处理和组织数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kevin_Erics

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值