数组
静态数组:int[]
float[]
double[]
char[]
string[]
特点:数组一旦创建,其容量的大小是无法改变的
int[] arr = new int[20]
动态数组:ArrayList
List
(泛型)
下面是使用数组实现的动态数组。
class Array1<T>
{
private T[] data; //存储元素的数组
private int N; //数组中的元素个数
//通过指定容量开辟数组空间
public Array1(int capacity)
{
data = new T[capacity];
N = 0;
}
//默认数组容量为10
public Array1() : this(10) { }
//获取数组容量的属性
public int Capacity
{
get { return data.Length; }
}
//获取数组元素个数的属性
public int Count
{
get { return N; }
}
//判断数组是否为空的属性
public bool IsEmpty
{
get { return N == 0; }
}
//在数组指定位置添加元素e
public void Add(int index,T e)
{
if(index<0 || index > N)
throw new ArgumentException("数组索引越界");
if (N == data.Length)
ResetCapacity(2 * data.Length);
for (int i = N-1; i >= index; i--)
data[i + 1] = data[i];
data[index] = e;
N++;
}
//在数组尾部添加元素e
public void AddLast(T e)
{
Add(N, e);
}
//在数组头部添加元素e
public void AddFirst(T e)
{
Add(0, e);
}
//获取指定位置的元素e
public T Get(int index)
{
if(index<0 || index>=N)
throw new ArgumentException("数组索引越界");
return data[index];
}
//获取数组头部的元素e
public T GetFirst()
{
return Get(0);
}
//获取数组尾部的元素e
public T GetLast()
{
return Get(N - 1);
}
//修改数组中的值
public void Set(int index,T newE)
{
if (index < 0 || index >= N)
throw new ArgumentException("数组索引越界");
data[index] = newE;
}
//查询数组是否包含元素e
public bool Contains(int e)
{
for (int i = 0; i < N; i++)
{
if (data[i].Equals(e))
return true;
}
return false;
}
//查询数组中元素e的位置索引
public int IndexOf(int e)
{
for (int i = 0; i < N; i++)
{
if (data[i].Equals(e))
return i;
}
return -1;
}
//删除指定位置的元素并返回该元素
public T RemoveAt(int index)
{
if (index < 0 || index >= N)
throw new ArgumentException("索引超出了数组界限");
T del = data[index];
for (int i = index+1; i <= N-1 ; i++)
data[i - 1] = data[i];
N--;
data[N] = default(T);
//这里是为了防止经常扩容做的处理
if (N == data.Length / 4)
ResetCapacity(data.Length / 2);
return del;
}
//删除数组头部位置的元素并返回该元素
public T RemoveFirst()
{
return RemoveAt(0);
}
//删除数组尾部位置的元素并返回该元素
public T RemoveLast()
{
return RemoveAt(N - 1);
}
//删除指定的元素e
public void Remove(int e)
{
int index = IndexOf(e);
if (index != -1)
RemoveAt(index);
}
//调整数组容量的大小
private void ResetCapacity(int newCapacity)
{
T[] newData = new T[newCapacity];
for (int i = 0; i < N; i++)
newData[i] = data[i];
data = newData;
}
//输出类的信息
public override string ToString()
{
StringBuilder res = new StringBuilder();
res.Append(string.Format("Array1: count={0} capacity={1}\n", N, data.Length));
res.Append("[");
for (int i = 0; i < N; i++)
{
res.Append(data[i]);
if (i != N - 1)
res.Append(", ");
}
res.Append("]");
return res.ToString();
}
}
这里使用泛型是为了防止拆箱装箱消耗性能,可以直接指定类型。不懂泛型用法的需要复习下C#基础。
装箱:值类型转换为引用类型
拆箱:引用类型转换为值类型
引用类型:任何称为“类”的类型都是引用类型,使用class
修饰。如string
object
值类型:所有值类型都称为结构或枚举,使用struct
或enum
修饰。如int
float
double
char
可以直接转到定义(F12或ctrl + 鼠标左键)查看是什么修饰来区分。
ArrayList
是objec
t数组。
List
是泛型数组,性能更好,可以指定类型不需要拆装箱转换。
链表
单向链表
链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始;链表是使用指针进行构造的列表;又称为结点列表,因为链表是由一个个结点组装起来的;其中每个结点都有指针成员变量指向列表中的下一个结点;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DataStructure
{
//单链表
class LinkedList1<T>
{
//私有的内部结点类
private class Node
{
public T e; //结点中存储的元素
public Node next; //存储下一个结点的引用
//通过元素和下一个结点的引用创建结点
public Node(T e, Node next)
{
this.e = e;
this.next = next;
}
//通过元素创建结点
public Node(T e)
{
this.e = e;
this.next = null;
}
//输出结点的信息
public override string ToString()
{
return e.ToString();
}
}
//头指针标识链表的头部
private Node head;
//链表中元素的个数
private int N;
//链表初始化一个结点也没有,head指向空,N为0
public LinkedList1()
{
head = null;
N = 0;
}
//获取链表中元素的个数
public int Count
{
get { return N; }
}
//判断链表是否为空
public bool IsEmpty
{
get { return N == 0; }
}
//往链表添加元素
public void Add(int index, T e)
{
if (index < 0 || index > N)
throw new ArgumentException("非法索引");
if (index == 0)
{
head = new Node(e, head);
}
else
{
Node pre = head;
for (int i = 0; i < index - 1; i++)
pre = pre.next;
pre.next = new Node(e, pre.next);
}
N++;
}
//在链表的头部添加元素
public void AddFirst(T e)
{
Add(0, e);
}
//在链表的尾部添加元素
public void AddLast(T e)
{
Add(N, e);
}
//获取链表第index位置的元素(从0开始计算)
public T Get(int index)
{
if (index < 0 || index >= N)
throw new ArgumentException("非法索引");
Node cur = head;
for (int i = 0; i < index; i++)
cur = cur.next;
return cur.e;
}
//获取链表头部元素
public T GetFirst()
{
return Get(0);
}
//获取链表尾部元素
public T GetLast()
{
return Get(N - 1);
}
//修改链表元素的值
public void Set(int index, T newE)
{
if (index < 0 || index >= N)
throw new ArgumentException("非法索引");
Node cur = head;
for (int i = 0; i < index; i++)
cur = cur.next;
cur.e = newE;
}
//查询链表中是否包含元素e
public bool Contains(T e)
{
Node cur = head;
while (cur != null)
{
if (cur.e.Equals(e))
return true;
cur = cur.next;
}
return false;
}
//删除链表第index位置的元素(从0开始计算)
public T RemoveAt(int index)
{
if (index < 0 || index >= N)
throw new ArgumentException("非法索引");
if (index == 0)
{
Node delNode = head;
head = head.next;
N--;
return delNode.e;
}
else
{
Node pre = head;
for (int i = 0; i < index - 1; i++)
pre = pre.next;
Node delNode = pre.next;
pre.next = delNode.next;
N--;
return delNode.e;
}
}
//删除链表的头部元素
public T RemoveFirst()
{
return RemoveAt(0);
}
//删除链表的尾部元素
public T RemoveLast()
{
return RemoveAt(N - 1);
}
//删除链表指点的元素e
public void Remove(T e)
{
if (head == null)
return;
if (head.e.Equals(e))
{
head = head.next;
N--;
}
else
{
Node cur = head;
Node pre = null;
while (cur != null)
{
if (cur.e.Equals(e))
break;
pre = cur;
cur = cur.next;
}
if (cur != null)
{
pre.next = pre.next.next;
N--;
}
}
}
//打印链表
public override string ToString()
{
StringBuilder res = new StringBuilder();
Node cur = head;
while (cur != null)
{
res.Append(cur + "->");
cur = cur.next;
}
res.Append("Null");
return res.ToString();
}
}
}
双向链表
和单链表一样,双链表也是由节点组成,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
/// <summary>
/// 双向链表节点
/// </summary>
/// <typeparam name="T"></typeparam>
public class BdNode<T>
{
public T Data { set; get; }
public BdNode<T> Next { set; get; }
public BdNode<T> Prev { set; get; }
public BdNode(T val, BdNode<T> prev, BdNode<T> next)
{
this.Data = val;
this.Prev = prev;
this.Next = next;
}
}
public class DoubleLink<T>
{
//表头
private readonly BdNode<T> _linkHead;
//节点个数
private int _size;
public DoubleLink()
{
_linkHead = new BdNode<T>(default(T), null, null);//双向链表 表头为空
_linkHead.Prev = _linkHead;
_linkHead.Next = _linkHead;
_size = 0;
}
public int GetSize() => _size;
public bool IsEmpty() => (_size == 0);
//通过索引查找
private BdNode<T> GetNode(int index)
{
if (index < 0 || index >= _size)
throw new IndexOutOfRangeException("索引溢出或者链表为空");
if (index < _size / 2)//正向查找
{
BdNode<T> node = _linkHead.Next;
for (int i = 0; i < index; i++)
node = node.Next;
return node;
}
//反向查找
BdNode<T> rnode = _linkHead.Prev;
int rindex = _size - index - 1;
for (int i = 0; i < rindex; i++)
rnode = rnode.Prev;
return rnode;
}
public T Get(int index) => GetNode(index).Data;
public T GetFirst() => GetNode(0).Data;
public T GetLast() => GetNode(_size - 1).Data;
// 将节点插入到第index位置之前
public void Insert(int index, T t)
{
if (_size < 1 || index >= _size)
throw new Exception("没有可插入的点或者索引溢出了");
if (index == 0)
Append(_size, t);
else
{
BdNode<T> inode = GetNode(index);
BdNode<T> tnode = new BdNode<T>(t, inode.Prev, inode);
inode.Prev.Next = tnode;
inode.Prev = tnode;
_size++;
}
}
//追加到index位置之后
public void Append(int index, T t)
{
BdNode<T> inode;
if (index == 0)
inode = _linkHead;
else
{
index = index - 1;
if (index < 0)
throw new IndexOutOfRangeException("位置不存在");
inode = GetNode(index);
}
BdNode<T> tnode = new BdNode<T>(t, inode, inode.Next);
inode.Next.Prev = tnode;
inode.Next = tnode;
_size++;
}
public void Del(int index)
{
BdNode<T> inode = GetNode(index);
inode.Prev.Next = inode.Next;
inode.Next.Prev = inode.Prev;
_size--;
}
public void DelFirst() => Del(0);
public void DelLast() => Del(_size - 1);
public void ShowAll()
{
Console.WriteLine("******************* 链表数据如下 *******************");
for (int i = 0; i < _size; i++)
Console.WriteLine("(" + i + ")=" + Get(i));
Console.WriteLine("******************* 链表数据展示完毕 *******************\n");
}
}
时间复杂度
栈与队列
栈(Stack)
是一种后进先出的数据结构,只能一端进行添加(入栈)或删除(出栈)操作,这一端称为栈顶。例如手枪弹匣。
队列(Queue)
是一种先进先出的数据结构。只允许在队头删除元素(出队),在队尾添加元素(入队)。例如排队。
数组栈
利用前面实现过的动态数组实现数组栈。
这里添加数据是在末尾添加的,因为在Last添加时间复杂度是O(1) ,在First添加时间复杂度是O(n)。
//首先用接口规范约束
interface IStack<T>
{
//数量
int Count { get; }
//是否为空
bool IsEmpty { get; }
//入栈
void Push(T e);
//出栈
T Pop();
//查看栈顶元素
T Peek();
}
//数组栈
class Array1Stack<T>:IStack<T>
{
//动态数组作为底层的数据结构
private Array1<T> arr;
//初始化容量为capacity的数组栈
public Array1Stack(int capacity)
{
arr = new Array1<T>(capacity);
}
//使用默认容量的数组栈
public Array1Stack()
{
arr = new Array1<T>();
}
//获取栈中元素个数 O(1)
public int Count { get { return arr.Count; } }
//判断栈是否为空 O(1)
public bool IsEmpty { get { return arr.IsEmpty; } }
//往栈顶添加元素 O(1)
public void Push(T e)
{
arr.AddLast(e);
}
//删除栈顶元素 O(1)
public T Pop()
{
return arr.RemoveLast();
}
//查看栈顶元素 O(1)
public T Peek()
{
return arr.GetLast();
}
//打印栈信息
public override string ToString()
{
return "Stack: " + arr.ToString() + "top";
}
}
链表栈
利用链表实现栈结构。
这里添加栈用的是AddFirst()
,与数组正好相反,AddFirst()
时间复杂度是O(1),AddLast()
时间复杂度是O(n)。
//链表栈
class LinkedList1Stack<T>:IStack<T>
{
//链表作为底层数据结构存储元素
private LinkedList1<T> list;
//构造函数初始化链表
public LinkedList1Stack()
{
list = new LinkedList1<T>();
}
//获取栈中元素个数 O(1)
public int Count { get { return list.Count; } }
//判断栈是否为空 O(1)
public bool IsEmpty { get { return list.IsEmpty; } }
//往栈顶添加元素 O(1)
public void Push(T e)
{
list.AddFirst(e);
}
//删除栈顶元素 O(1)
public T Pop()
{
return list.RemoveFirst();
}
//查看栈顶元素 O(1)
public T Peek()
{
return list.GetFirst();
}
//打印链表栈信息
public override string ToString()
{
return "Stack: top " + list.ToString();
}
}
实际测试了下数组栈比链表栈性能要好一些,因为链表创建数据(数据、指针)和访问数据(遍历)更加耗时。
数组队列
//队列接口
interface IQueue<T>
{
int Count { get; }
bool IsEmpty { get; }
void Enqueue(T e);
T Dequeue();
T Peek();
}
//数组队列
class Array1Queue<T>:IQueue<T>
{
//动态数组作为底层的数据结构
private Array1<T> arr;
//创建容量为capacity的队列
public Array1Queue(int capacity)
{
arr = new Array1<T>(capacity);
}
//使用默认的容量创建队列
public Array1Queue()
{
arr = new Array1<T>();
}
//获取队列元素的个数 O(1)
public int Count { get { return arr.Count; } }
//查看队列是否为空 O(1)
public bool IsEmpty { get { return arr.IsEmpty; } }
//入队。往队尾添加元素 O(1)
public void Enqueue(T e)
{
arr.AddLast(e);
}
//出队。删除队首的元素 O(n)
public T Dequeue()
{
return arr.RemoveFirst();
}
//查看队首的元素 O(1)
public T Peek()
{
return arr.GetFirst();
}
//打印数组队列信息
public override string ToString()
{
return "Queue: front " + arr.ToString() + " tail";
}
}
循环队列
为充分利用向量空间,克服"假溢出"现象的方法是:将向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量。存储在其中的队列称为循环队列(Circular Queue)。循环队列是把顺序队列首尾相连,把存储队列元素的表从逻辑上看成一个环,成为循环队列。
当数组内有空缺位置时新添加数据优先补充空缺位置。当存储位置满了之后会进行扩容,然后将之前的数组移动到新数组重新排序并调整first与last索引。
//队列接口
interface IQueue<T>
{
int Count { get; }
bool IsEmpty { get; }
void Enqueue(T e);
T Dequeue();
T Peek();
}
class Array2<T>
{
private T[] data;
private int first;
private int last;
private int N;
public Array2(int capacity)
{
data = new T[capacity];
first = 0;
last = 0;
N = 0;
}
public Array2() : this(10) { }
public int Count { get { return N; } }
public bool IsEmpty { get { return N == 0; } }
public void AddLast(T e)
{
if (N == data.Length)
ResetCapacity(2 * data.Length);
data[last] = e;
last = (last + 1) % data.Length;
N++;
}
public T RemoveFirst()
{
if (IsEmpty)
throw new InvalidOperationException("数组为空");
T ret = data[first];
data[first] = default(T);
first = (first + 1) % data.Length;
N--;
if (N == data.Length / 4)
ResetCapacity(data.Length / 2);
return ret;
}
public T GetFirst()
{
if (IsEmpty)
throw new InvalidOperationException("数组为空");
return data[first];
}
private void ResetCapacity(int newCapacity)
{
T[] newData = new T[newCapacity];
for (int i = 0; i < N; i++)
newData[i] = data[(first + i) % data.Length];
data = newData;
first = 0;
last = N;
}
public override string ToString()
{
StringBuilder res = new StringBuilder();
res.Append("[");
for (int i = 0; i < N; i++)
{
res.Append(data[(first + i) % data.Length]);
if ((first + i + 1) % data.Length != last)
res.Append(",");
}
res.Append("]");
return res.ToString();
}
}
//数组队列
class Array2Queue<T> : IQueue<T>
{
//循环数组作为底层的数据结构
private Array2<T> arr;
//创建容量为capacity的队列
public Array2Queue(int capacity)
{
arr = new Array2<T>(capacity);
}
//使用默认的容量创建队列
public Array2Queue()
{
arr = new Array2<T>();
}
//获取队列元素的个数 O(1)
public int Count { get { return arr.Count; } }
//查看队列是否为空 O(1)
public bool IsEmpty { get { return arr.IsEmpty; } }
//入队。往队尾添加元素 O(1)
public void Enqueue(T e)
{
arr.AddLast(e);
}
//出队。删除队首的元素 O(1)
public T Dequeue()
{
return arr.RemoveFirst();
}
//查看队首的元素 O(1)
public T Peek()
{
return arr.GetFirst();
}
//打印数组队列信息
public override string ToString()
{
return "Queue: front " + arr.ToString() + " tail";
}
}
循环队列的Dequeue方法时间复杂度是O(1),不需要每次移动数据位置,所以性能要比数组队列好很多。
链表队列及优化
这里对原本的链表进行了优化,添加了记录尾部节点,获取队尾的时间复杂度优化为O(1),之前版本是O(n)。
//第二版本的链表类,具备头尾指针
class LinkedList2<E>
{
private class Node
{
public E e;
public Node next;
public Node(E e, Node next)
{
this.e = e;
this.next = next;
}
public Node(E e)
{
this.e = e;
this.next = null;
}
public override string ToString()
{
return e.ToString();
}
}
private Node head;
private Node tail; //尾指针标记链表尾部结点
private int N;
public LinkedList2()
{
head = null;
tail = null;
N = 0;
}
public int Count { get { return N; } }
public bool IsEmpty { get { return N == 0; } }
public void AddLast(E e)
{
Node node = new Node(e);
if (IsEmpty)
{
head = node;
tail = node;
}
else
{
tail.next = node;
tail = node;
}
N++;
}
public E RemoveFirst()
{
if (IsEmpty)
throw new InvalidOperationException("链表为空");
E e = head.e;
head = head.next;
N--;
if (head == null)
tail = null;
return e;
}
public E GetFirst()
{
if (IsEmpty)
throw new InvalidOperationException("链表为空");
return head.e;
}
public override string ToString()
{
StringBuilder res = new StringBuilder();
Node cur = head;
while (cur != null)
{
res.Append(cur + "->");
cur = cur.next;
}
res.Append("Null");
return res.ToString();
}
}
class LinkedList2Queue<T> : IQueue<T>
{
private LinkedList2<T> list;
public LinkedList2Queue()
{
list = new LinkedList2<T>();
}
//O(1)
public int Count { get { return list.Count; } }
//O(1)
public bool IsEmpty { get { return list.IsEmpty; } }
//O(1)
public T Dequeue()
{
return list.RemoveFirst();
}
//O(1)
public void Enqueue(T e)
{
list.AddLast(e);
}
//O(1)
public T Peek()
{
return list.GetFirst();
}
public override string ToString()
{
return "Queue front " + list.ToString() + " tail";
}
}
集合和映射
集合(Set)
作为存储数据的容器时:它是不允许存储相同元素的。只能保留一份。能快速的帮助我们进行去重操作,过滤掉重复的元素。
例如词汇量统计就是典型的应用。统计一篇英文文章的总单词数,使用集合进行去重。看看不同的单词总数有多少个。
基于无序链表实现集合
interface ISet<T>
{
int Count { get; }
bool IsEmpty { get; }
void Add(T e);
void Remove(T e);
bool Contains(T e);
}
class LinkedList1Set<T>:ISet<T>
{
private LinkedList1<T> list;
public LinkedList1Set()
{
list = new LinkedList1<T>();
}
//O(1)
public int Count { get { return list.Count; } }
//O(1)
public bool IsEmpty { get { return list.IsEmpty; } }
//O(n)
public void Add(T e)
{
if (!list.Contains(e))
list.AddFirst(e);
}
//O(n)
public bool Contains(T e)
{
return list.Contains(e);
}
//O(n)
public void Remove(T e)
{
list.Remove(e);
}
}
映射
指两个元素之间相互“对应”的关系。
在C#中使用字典(Dictionary)表示,存储键值对的数据。
interface IDictionary<Key,Value>
{
int Count { get; }
bool IsEmpty { get; }
void Add(Key key, Value value);
void Remove(Key key);
bool ContainsKey(Key key);
Value Get(Key key);
void Set(Key key, Value newValue);
}
//第三版的链表类,只具备头指针,存储键值对的数据
class LinkedList3<Key,Value>
{
//私有的内部结点类
//这个类相比较以前的LinkedList1的结点类多存储了一个value变量而已
private class Node
{
public Key key; //键
public Value value; //值
public Node next;
public Node (Key key,Value value,Node next)
{
this.key = key;
this.value = value;
this.next = next;
}
public override string ToString()
{
return key.ToString() + " : " + value.ToString();
}
}
private Node head;
private int N;
public LinkedList3()
{
head = null;
N = 0;
}
public int Count { get { return N; } }
public bool IsEmpty { get { return N == 0; } }
//私有的辅助函数,通过键查找对应的结点
//有了它作为前提,能更方便的实现增删改查相应的功能
private Node GetNode(Key key)
{
Node cur = head;
while (cur != null)
{
if (cur.key.Equals(key))
return cur;
cur = cur.next;
}
return null;
}
//往链表添加键值对数据
public void Add(Key key,Value value)
{
Node node = GetNode(key);
//如果结点不存在,则创建新节点
if (node == null)
{
head = new Node(key, value, head);
N++;
}
else //存在的话,就更新一下键对应的值
node.value = value;
}
//查看链表中是否包含指定键
public bool Contains(Key key)
{
return GetNode(key) != null;
}
//通过键获取对应的值
public Value Get(Key key)
{
Node node = GetNode(key);
if (node == null)
throw new ArgumentException("键" + key + "不存在");
else
return node.value;
}
//将键对应的值改成一个新的值
public void Set(Key key,Value newValue)
{
Node node = GetNode(key);
if (node == null)
throw new ArgumentException("键" + key + "不存在");
else
node.value = newValue;
}
//通过键删除结点中存储的储键值对数据
public void Remove(Key key)
{
if (head == null)
return;
if (head.key.Equals(key))
{
head = head.next;
N--;
}
else
{
Node cur = head;
Node pre = null;
while (cur != null)
{
if (cur.key.Equals(key))
break;
pre = cur;
cur = cur.next;
}
if (cur != null)
{
pre.next = pre.next.next;
N--;
}
}
}
}
class LinkedList3Dictionary<Key,Value>:IDictionary<Key,Value>
{
private LinkedList3<Key, Value> list;
public LinkedList3Dictionary()
{
list = new LinkedList3<Key, Value>();
}
//O(1)
public int Count { get { return list.Count; } }
//O(1)
public bool IsEmpty { get { return list.IsEmpty; } }
//O(1)
public void Add(Key key, Value value)
{
list.Add(key, value);
}
//O(n)
public bool ContainsKey(Key key)
{
return list.Contains(key);
}
//O(n)
public Value Get(Key key)
{
return list.Get(key);
}
//O(n)
public void Remove(Key key)
{
list.Remove(key);
}
//O(n)
public void Set(Key key, Value newValue)
{
list.Set(key, newValue);
}
}
有序数组和二分查找
顺序查找法
按顺序遍历对比。
二分查找法
只能对有序排列进行高效查找(排序算法的作用)。
不用担心数据奇数偶数问题,因为都会遍历到(int计算后还是int不会有小数)。
操作步骤
//二分查找法,在有序数组arr中,查找target
//如果找到target,返回相应的索引index
//如果没有找到target,返回-1
public static int BinarySearch(int[] arr,int target)
{
//在[l...r]范围里寻找target
int l = 0;
int r = arr.Length - 1;
while (l <= r)
{
//int mid = (r + l) / 2;
int mid = l + (r - l) / 2;
if (target < arr[mid])
r = mid - 1; //在arr[l...mid-1]查找target
else if (target > arr[mid])
l = mid + 1; //在arr[mid+1...r]查找target
else
return mid; //找到target,并返回索引
}
return -1;
}
有序数组
有序数组即为用数组与二分查找法实现的按一定顺序排列的数据结构。
有序数组添加删除元素
//有序数组
//存储的元素必须是可比较的。这样才能进行排序。
//数据类型Key必须是实现了可比较的接口IComparable<Key>,才能进行元素的存储。
//where Key:IComparable<Key> 对Key进行泛型约束,限定Key类型,不能是任意类型
class SortedArray1<Key> where Key:IComparable<Key>
{
private Key[] keys;
private int N;
public SortedArray1(int capacity)
{
keys = new Key[capacity];
}
public SortedArray1() : this(10) { }
public int Count { get { return N; } }
public bool IsEmpty { get { return N == 0; } }
public int Rank(Key key)
{
//在[l...r]范围里寻找key
int l = 0;
int r = N - 1;
while (l <= r)
{
//int mid = (r + l) / 2;
int mid = l + (r - l) / 2;
if (key.CompareTo(keys[mid]) < 0)
r = mid - 1; //在keys[l...mid-1]查找key
else if (key.CompareTo(keys[mid]) > 0)
l = mid + 1; //在keys[mid+1...r]查找key
else //key.CompareTo(keys[mid]) == 0
return mid; //找到key,并返回索引
}
return l;
}
public void Add(Key key)
{
int i = Rank(key);
if (N == keys.Length)
ResetCapacity(2 * keys.Length);
if ( i < N && keys[i].CompareTo(key) == 0)
return;
for (int j = N-1; j >= i; j--)
keys[j + 1] = keys[j];
keys[i] = key;
N++;
}
public void Remove(Key key)
{
if (IsEmpty)
return;
int i = Rank(key);
if (i==N || keys[i].CompareTo(key) != 0)
return;
for (int j = i+1; j <= N-1; j++)
keys[j - 1] = keys[j];
N--;
keys[N] = default(Key);
if (N == keys.Length / 4)
ResetCapacity(keys.Length / 2);
}
public Key Min()
{
if (IsEmpty)
throw new ArgumentException("数组为空");
return keys[0];
}
public Key Max()
{
if (IsEmpty)
throw new ArgumentException("数组为空");
return keys[N - 1];
}
public Key Select(int k)
{
if (k < 0 || k >= N)
throw new ArgumentException("索引越界");
return keys[k];
}
public bool Contains(Key key)
{
int i = Rank(key);
if (i < N && keys[i].CompareTo(key) == 0)
return true;
return false;
}
//找出小于或等于key的最大键
public Key Floor(Key key)
{
int i = Rank(key);
if (i < N && keys[i].CompareTo(key) == 0)
return keys[i];
if (i == 0)
throw new ArgumentException("没有找到小于或等于" + key + "的最大键");
else
return keys[i - 1];
}
//找出大于或等于key的最小键
public Key Ceiling(Key key)
{
int i = Rank(key);
if (i == N)
throw new ArgumentException("没有找到大于或等于" + key + "的最小键");
else
return keys[i];
}
//调整数组容量的大小
private void ResetCapacity(int newCapacity)
{
Key[] newKeys = new Key[newCapacity];
for (int i = 0; i < N; i++)
newKeys[i] = keys[i];
keys = newKeys;
}
//输出数组类的信息
public override string ToString()
{
StringBuilder res = new StringBuilder();
res.Append("[");
for (int i = 0; i < N; i++)
{
res.Append(keys[i]);
if (i != N - 1)
res.Append(", ");
}
res.Append("]");
return res.ToString();
}
}
基于有序数组实现集合
class SortedArray1Set<Key>:ISet<Key> where Key:IComparable<Key>
{
private SortedArray1<Key> s;
public int Count { get { return s.Count; } }
public bool IsEmpty { get { return s.IsEmpty; } }
public SortedArray1Set(int capacity)
{
s = new SortedArray1<Key>();
}
public SortedArray1Set()
{
s = new SortedArray1<Key>();
}
//O(n)
public void Add(Key key)
{
s.Add(key);
}
//O(n)
public void Remove(Key key)
{
s.Remove(key);
}
//O(log n)
public bool Contains(Key key)
{
return s.Contains(key);
}
}
基于有序数组实现映射
//有序数组
//存储的元素必须是可比较的。这样才能进行排序。
//数据类型Key必须是实现了可比较的接口IComparable<Key>,才能进行元素的存储。
//where Key:IComparable<Key> 对Key进行泛型约束,限定Key类型,不能是任意类型
class SortedArray2<Key,Value> where Key : IComparable<Key>
{
private Key[] keys;
private Value[] values;
private int N;
public SortedArray2(int capacity)
{
keys = new Key[capacity];
values = new Value[capacity];
}
public SortedArray2() : this(10) { }
public int Count { get { return N; } }
public bool IsEmpty { get { return N == 0; } }
public int Rank(Key key)
{
//在[l...r]范围里寻找key
int l = 0;
int r = N - 1;
while (l <= r)
{
//int mid = (r + l) / 2;
int mid = l + (r - l) / 2;
if (key.CompareTo(keys[mid]) < 0)
r = mid - 1; //在keys[l...mid-1]查找key
else if (key.CompareTo(keys[mid]) > 0)
l = mid + 1; //在keys[mid+1...r]查找key
else //key.CompareTo(keys[mid]) == 0
return mid; //找到key,并返回索引
}
return l;
}
public Value Get(Key key)
{
if (IsEmpty)
throw new ArgumentException("数组为空");
int i = Rank(key);
if (i < N && keys[i].CompareTo(key) == 0)
return values[i];
else
throw new ArgumentException("键"+key+"不存在");
}
public void Add(Key key,Value value)
{
int i = Rank(key);
if (N == keys.Length)
ResetCapacity(2 * keys.Length);
if (i < N && keys[i].CompareTo(key) == 0)
{
values[i] = value;
return;
}
for (int j = N - 1; j >= i; j--)
{
keys[j + 1] = keys[j];
values[j + 1] = values[j];
}
keys[i] = key;
values[i] = value;
N++;
}
public void Remove(Key key)
{
if (IsEmpty)
return;
int i = Rank(key);
if (i == N || keys[i].CompareTo(key) != 0)
return;
for (int j = i + 1; j <= N - 1; j++)
{
keys[j - 1] = keys[j];
values[j - 1] = values[j];
}
N--;
keys[N] = default(Key);
values[N] = default(Value);
if (N == keys.Length / 4)
ResetCapacity(keys.Length / 2);
}
public Key Min()
{
if (IsEmpty)
throw new ArgumentException("数组为空");
return keys[0];
}
public Key Max()
{
if (IsEmpty)
throw new ArgumentException("数组为空");
return keys[N - 1];
}
public Key Select(int k)
{
if (k < 0 || k >= N)
throw new ArgumentException("索引越界");
return keys[k];
}
public bool Contains(Key key)
{
int i = Rank(key);
if (i < N && keys[i].CompareTo(key) == 0)
return true;
return false;
}
//找出小于或等于key的最大键
public Key Floor(Key key)
{
int i = Rank(key);
if (i < N && keys[i].CompareTo(key) == 0)
return keys[i];
if (i == 0)
throw new ArgumentException("没有找到小于或等于" + key + "的最大键");
else
return keys[i - 1];
}
//找出大于或等于key的最小键
public Key Ceiling(Key key)
{
int i = Rank(key);
if (i == N)
throw new ArgumentException("没有找到大于或等于" + key + "的最小键");
else
return keys[i];
}
//调整数组容量的大小
private void ResetCapacity(int newCapacity)
{
Key[] newKeys = new Key[newCapacity];
Value[] newValues = new Value[newCapacity];
for (int i = 0; i < N; i++)
{
newKeys[i] = keys[i];
newValues[i] = values[i];
}
keys = newKeys;
values = newValues;
}
//输出数组类的信息
public override string ToString()
{
StringBuilder res = new StringBuilder();
res.Append("[");
for (int i = 0; i < N; i++)
{
res.Append("{"+keys[i]+","+values[i]+"}");
if (i != N - 1)
res.Append(", ");
}
res.Append("]");
return res.ToString();
}
}
class SortedArray2Dictionary<Key,Value>:IDictionary<Key,Value> where Key:IComparable<Key>
{
private SortedArray2<Key, Value> s2;
public int Count { get { return s2.Count; } }
public bool IsEmpty { get { return s2.IsEmpty; } }
public SortedArray2Dictionary(int capacity)
{
s2 = new SortedArray2<Key, Value>(capacity);
}
public SortedArray2Dictionary()
{
s2 = new SortedArray2<Key, Value>();
}
//O(n)
public void Add(Key key, Value value)
{
s2.Add(key, value);
}
//O(n)
public void Remove(Key key)
{
s2.Remove(key);
}
//O(log n)
public bool ContainsKey(Key key)
{
return s2.Contains(key);
}
//O(log n)
public Value Get(Key key)
{
return s2.Get(key);
}
//O(log n)
public void Set(Key key, Value newValue)
{
s2.Add(key, newValue);
}