Blog


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 日程表

  • 站点地图

  • 公益404

  • 搜索

单向环形链表

发表于 2019-11-17 | 分类于 数据结构 | 阅读次数:
字数统计: 776

一:单向环形链表应用场景

1573998435002

二:分析思路:

1.自己分析:

给链表编辑一个add(),一个del(),获取链表长度getlength(),默认从1开始数。

  1. 初始化的时候,通过Add方法读取在数据环中,一次加载,接下来。

  2. 做while遍历,当读取到m的时候,首先检查链表的长度,如果长度大于1就移除一个元素,当长度等于1的时候跳出循环

    代码实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    /// <summary>
    /// 删除节点
    /// </summary>
    /// <param name="no">节点的值</param>
    public void del(int no)
    {
    Hero temp = gethead();
    if (no== temp.no)
    {
    if (head.next==foot)
    {
    Change();
    }
    else
    {
    temp = foot;
    temp.next = temp.next.next;
    head = temp.next;
    }
    }
    else
    {
    temp = getpre(head, no);
    temp.next = temp.next.next;
    Change();
    }

    }
    /// <summary>
    /// 只有2个节点的时候,直接出
    /// </summary>
    public void Change()
    {
    if (head.next == foot)
    {
    Console.Write(head);
    head = foot;
    head.next = null;
    }
    }
    /// <summary>
    /// 获取待删除节点的上一个节点
    /// </summary>
    /// <param name="temp">头节点的</param>
    /// <param name="no">要删除的节点</param>
    /// <returns></returns>
    public Hero getpre(Hero temp,int no)
    {
    while (true)
    {
    if (temp.next.no == no)
    {
    break;
    }
    temp = temp.next;
    }
    if (temp.next==foot)
    {
    foot = temp;
    }
    return temp;
    }
    /// <summary>
    /// 获取链表的长度
    /// </summary>
    /// <returns></returns>
    public int getlength()
    {
    Hero temp = head;
    int num = 0;
    while (true)
    {
    num++;
    temp = temp.next;
    if (temp.no==head.no)
    {
    break;
    }
    }
    return num;
    }
    /// <summary>
    /// 输出打印
    /// </summary>
    /// <param name="m">数m下</param>
    public void showlink(int m)
    {
    Hero temp = head;
    int num = 0;
    int leng = getlength();//获取链表的长度
    if (head == null)
    {
    Console.WriteLine("参数输入有误,请重新输入!");
    return;
    }
    while (true)
    {
    if (temp.next==null)
    {
    break;
    }
    num++;
    if (num%m==0)
    {
    Console.WriteLine(temp.no);
    del(temp.no);
    }
    temp = temp.next;
    }
    Console.WriteLine(temp);
    }

2.老师分析:

1574042353065

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/// <summary>
/// 老师思路的实现
/// </summary>
/// <param name="startNo">开始的编号</param>
/// <param name="countNum">间隔个数(数几下)</param>
/// <param name="nums">链表长度</param>
public void countBoy(int startNo,int countNum,int nums)
{
//先对数据进行校验
if (head==null||startNo<1||startNo>nums)
{
Console.WriteLine("参数输入有误,请重新输入!");
return;
}
//创建辅助指针,小孩出圈
Hero helper = head;//指向最有一个节点
while (true)
{
if (helper.next==head)
{
break;
}
helper = helper.next;
}
//小孩报数钱,先让head和helper移动k-1次
for (int i = 0; i < startNo-1; i++)
{
head = head.next;
helper = helper.next;
}
//当小孩报数时,让head和helper指针同时的移动m-1次,然后出圈
//这里是一个循环,循环到只有一个节点的时候
while (true)
{
if (helper==head)
{
break;
}
//让head和helper指针同时的移动countNum - 1次
for (int i = 0; i < countNum - 1; i++)
{
head = head.next;
helper = helper.next;
}
Console.WriteLine("小孩出圈:"+head.no);
//将head指向的小孩出圈
head = head.next;
helper.next = head;
}
Console.WriteLine("最后一个小孩:"+ head.no);
}

1574042483920

三:源码地址

GitHub源码地址

双向链表

发表于 2019-11-15 | 分类于 数据结构 | 阅读次数:
字数统计: 870

一:为什么要使用双向链表?

  1. 单向链表,查找的方向只能是一个方向,二双向链表可以向前或向后查询。
  2. 单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,所以我们单链表删除节点时,总是找到temp,temp是待删除节点的前一个节点。

二:分析双向链表的增、删、改、查、遍历的功能!

1573980862995

三:代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo4
{
public class DoubleLinkedList
{
//初始化一个头结点,头结点不要动,不存放具体的数据
private HeroNode head = new HeroNode(0, "", "");

//返回头结点
public HeroNode gethead()
{
return head;
}
//显示链表
public void showLink()
{
if (head.next == null)
{
Console.WriteLine("链表为空!");
}
//因为头结点不能懂,我们需要一个辅助变量来遍历
HeroNode temp = head.next;
while (true)
{
//找到链表的最后
if (temp.next == null)
{
Console.WriteLine(temp);
break;
}
Console.WriteLine(temp);
//将temp后移
temp = temp.next;
}
}
//添加节点到单向链表
public void add(HeroNode heroNode)
{
//因为head节点不能动,因此我们需要一个辅助遍历temp
HeroNode temp = head;
//遍历链表,找到最后
while (true)
{
//找到链表的最后
if (temp.next == null)
{
break;
}
//如果没有找到最后
temp = temp.next;
}
//当退出while循环时,temp就指向了链表的最后
temp.next = heroNode;
heroNode.pre = temp;
}
//按照编号添加
public void addByOrder(HeroNode heroNode)
{
HeroNode temp = head;
bool flag = false;//标志添加的编号是否存在,默认为false
while (true)
{
if (temp.next == null)//说明temp已经在链表的最后
{
break;
}
if (temp.next.no > heroNode.no)//位置找到,就在temp后面插入
{
break;

}
else if (temp.next.no == heroNode.no)
{
flag = true;//说明编号存在
break;
}
temp = temp.next;
}
//判断flag的值,如果flag==true,说明编号存在
if (flag)
{
Console.WriteLine("准备插入的英雄的编号{0}已经存在了,不能加入\n", heroNode.no);
}
else
{
//插入到temp后面
temp.next.pre = heroNode;
heroNode.next = temp.next;

temp.next = heroNode;
heroNode.pre = temp;
}
}
//修改结点的信息,根据编号no来修改,即no编号不能改
public void update(HeroNode newheroNode)
{
if (head.next == null)
{
Console.WriteLine("链表为空!");
}
else
{
HeroNode temp = head;
bool flag = false;//是否找到该结点
while (true)
{
if (temp == null)
{
break;
}
if (temp.no == newheroNode.no)
{
//找到
flag = true;
break;
}
temp = temp.next;
}
if (flag)
{
temp.name = newheroNode.name;
temp.nickname = newheroNode.nickname;
}
else
{
Console.WriteLine("没有找到编号{0}的结点,不能修改\n", newheroNode.no);
}
}
}
//删除结点
public void delete(int n)
{
if (head.next == null)
{
Console.WriteLine("链表为空!");
}
else
{
HeroNode temp = head;
bool flag = false;//是否找到该结点
while (true)
{
if (temp == null)
{
break;
}
if (temp.no == n)
{
//找到
flag = true;
break;
}
temp = temp.next;
}
if (flag)
{
temp.pre.next = temp.next;
if (temp.next!=null)
{
temp.next.pre = temp.pre;

}

}
else
{
Console.WriteLine("没有找到编号{0}的结点,不能修改\n", n);
}
}
}
}

public class HeroNode
{
public int no;
public string name;
public string nickname;
public HeroNode next;//指向下一个节点
public HeroNode pre;//指向下一个节点
//构造器
public HeroNode(int no, string name, string nickname)
{
this.no = no;
this.name = name;
this.nickname = nickname;
}
//为了现实方法,我们重新toString
public override string ToString()
{
return "HeroNode[no=" + no + ",name=" + name + ",nickname=" + nickname + "]";
}
}
}

四:代码地址

GitHub源码地址

单链表面试题

发表于 2019-11-15 | 分类于 数据结构 | 阅读次数:
字数统计: 713

一:单链表面试题

1.求单链表中有效节点的个数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int getLength()
{
if (head.next==null)
{
return 0;
}
int length = 0;
HeroNode temp = head;
while (temp.next != null)
{
length++;
temp = temp.next;
}
return length;
}

2.查询单链表中倒数第k个节点

思路分析:

  1. 首先得知道链表的长度:int length = getLength();

  2. 计算倒数的个数和长度的差值+1就是求解的节点【为什么要+1,数组是从0开始】

  3. 开始遍历,找到第三个节点就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void GetBackByOne(int k)
{
if (head.next == null)
{
Console.WriteLine("当前链表为空!");
}
int length = getLength();
if (k>length||k<=0)
{
Console.WriteLine("您输入的个数超过了链表长度,无法查询!");
}
int num = length - k+1;
int count = 0;
HeroNode temp = head;
for(int i=0;i<num;i++)
{
temp=temp.next;
}
Console.WriteLine(temp);
}

3.单链表的反转:

自己的傻吊思路:

将整个链表读取出来,重新创建一个新的链表,重新赋值

代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public void fanzhuan()
{
if (head.next == null)
{
Console.WriteLine("当前链表为空!");
}
HeroNode temp = head;
List<HeroNode> list = new List<HeroNode>();
while (temp.next != null)
{
if (temp.no!=0)
{
list.Add(temp);
temp = temp.next;
}
else
{
temp = temp.next;
}
}
list.Add(temp);
head.next = null;
for (int i = list.Count-1; i >=0; i--)
{
list[i].next = null;
add(list[i]);
}
}
大佬的思路:

1.先定义一个新的节点reverseHead=new HeroNode();

2.从头到尾遍历原来的链表,每遍历一个节点,并放在新的链表的最前端

3.原来的链表的head.next=reverseHead.next

1573829092659

代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public void fanzhuan2(HeroNode head)
{
if (head.next == null || head.next.next == null)
{
return;
}
HeroNode cur=head.next;//获取下一个节点
HeroNode next = null;
HeroNode reverseHead = new HeroNode(0, "", "");//新建一个节点
while (cur.next != null)
{
next = cur.next;//取下一个节点
change(ref reverseHead,cur);//插入新节点
if (next == null)
{
break;
}
cur = next;//遍历下个节点
}
change(ref reverseHead, next);
//将头节点替换一下大功完成!!!
head.next = reverseHead.next;
}
public void change(ref HeroNode reverseHead,HeroNode cur)
{
HeroNode next = null;
next = reverseHead.next;
reverseHead.next = cur;
cur.next = next;
}

4.从尾到头打印单链表

方式一:借助栈的特性,先进后出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void Print()
{
if (head.next == null)
{
Console.WriteLine("当前链表为空!");
}
Stack<HeroNode> nodes = new Stack<HeroNode>();
HeroNode temp = head.next;
while (temp.next!=null)
{
nodes.Push(temp);
temp = temp.next;
}
nodes.Push(temp);

int count = nodes.Count;
for (int i = 0; i < count; i++)
{
Console.WriteLine(nodes.Pop());
}
}

方式二:

思路:

逆向打印单链表

  1. 先将单链表进行反正操作,然后在便利即可;【会破坏原链表的结构,不可取】参考上面3即可

5.合并两个有序的单链表,合并之后的链表依然有序

1
2
3
4
5
public void hebing(SingleLinkedList reverseHead,ref SingleLinkedList reverseHead2)
{
HeroNode hero = reverseHead.getHead().next;
reverseHead2.add(hero);
}

6.代码地址:

Github地址

数据结构-链表

发表于 2019-11-13 | 分类于 数据结构 | 阅读次数:
字数统计: 1.1k

一:介绍链表

链表是有序的列表,单是它在内存中存储如下:

1573649162803

  • 链表是以节点的方式存储。
  • 每个节点包含data 域,next域:指向下一个节点。
  • 发现链表的各个节点不一定是连续存放。
  • 链表分带头节点的链表和没有头结点的链表,根据实际的需求来确定。

二:单链表

1573649974144

逻辑结构示意图:

三:应用实例

1573699922714

使用带head头的单向链表实现—水浒英雄排行榜管理完成对英雄任务的增删改查操作

  1. 第一种方法在添加英雄时,直接添加到链表的尾部

思路分析:

1573700123856

  1. 第二种方式的添加英雄时,根据排名将英雄插入指定位置

思路分析:

1573700198225

  1. 修改结点的功能

    思路分析:

    先找到该结点,通过遍历

    通过 temp.name = newheroNode.name; temp.nickname = newheroNode.nickname;

  2. 删除结点的功能

    思路分析:

    1573700341117

四:代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
public class SingleLinkedList
{
//初始化一个头结点,头结点不要动,不存放具体的数据
private HeroNode head = new HeroNode(0,"","");
//添加节点到单向链表
//思路,当不考虑编号顺序是
//1.找到当前链表的最后节点
//2.将最后这个节点的next指向新的节点
public void add(HeroNode heroNode)
{
//因为head节点不能动,因此我们需要一个辅助遍历temp
HeroNode temp = head;
//遍历链表,找到最后
while (true)
{
//找到链表的最后
if (temp.next==null)
{
break;
}
//如果没有找到最后
temp = temp.next;
}
//当退出while循环时,temp就指向了链表的最后
temp.next = heroNode;
}
//显示链表
public void showLink()
{
if (head.next==null)
{
Console.WriteLine("链表为空!");
}
//因为头结点不能懂,我们需要一个辅助变量来遍历
HeroNode temp = head.next;
while (true)
{
//找到链表的最后
if (temp.next == null)
{
Console.WriteLine(temp);
break;
}
Console.WriteLine(temp);
//将temp后移
temp = temp.next;
}
}
//第二种方式在添加英雄的时候,根据排名将英雄插入到指定位置
//如果有这个排名,则添加失败
public void addByOrder(HeroNode heroNode)
{
HeroNode temp = head;
bool flag = false;//标志添加的编号是否存在,默认为false
while (true)
{
if (temp.next==null)//说明temp已经在链表的最后
{
break;
}
if (temp.next.no>heroNode.no)//位置找到,就在temp后面插入
{
break;

}
else if(temp.next.no==heroNode.no)
{
flag = true;//说明编号存在
break;
}
temp = temp.next;
}
//判断flag的值,如果flag==true,说明编号存在
if (flag)
{
Console.WriteLine("准备插入的英雄的编号{0}已经存在了,不能加入\n",heroNode.no);
}
else
{
//插入到temp后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
//修改结点的信息,根据编号no来修改,即no编号不能改
public void update(HeroNode newheroNode)
{
if (head.next==null)
{
Console.WriteLine("链表为空!");
}
else
{
HeroNode temp = head;
bool flag = false;//是否找到该结点
while (true)
{
if (temp==null)
{
break;
}
if (temp.no==newheroNode.no)
{
//找到
flag = true;
break;
}
temp = temp.next;
}
if (flag)
{
temp.name = newheroNode.name;
temp.nickname = newheroNode.nickname;
}
else
{
Console.WriteLine("没有找到编号{0}的结点,不能修改\n",newheroNode.no);
}
}
}
//删除结点
public void delete(int n)
{
if (head.next == null)
{
Console.WriteLine("链表为空!");
}
else
{
HeroNode temp = head;
bool flag = false;//是否找到该结点
while (true)
{
if (temp == null)
{
break;
}
if (temp.no == n-1)
{
//找到
flag = true;
break;
}
temp = temp.next;
}
if (flag)
{
temp.next = temp.next.next;
}
else
{
Console.WriteLine("没有找到编号{0}的结点,不能修改\n", n);
}
}
}
//查询
public void Select(int n)
{
if (head.next == null)
{
Console.WriteLine("链表为空!");
}
else
{
HeroNode temp = head;
bool flag = false;//是否找到该结点
while (true)
{
if (temp == null)
{
break;
}
if (temp.no == n)
{
//找到
flag = true;
break;
}
temp = temp.next;
}
if (flag)
{
Console.WriteLine(temp);
}
else
{
Console.WriteLine("没有找到编号{0}的结点,不能修改\n", n);
}
}
}

}
public class HeroNode
{
public int no;
public string name;
public string nickname;
public HeroNode next;//指向下一个节点
//构造器
public HeroNode(int no, string name, string nickname)
{
this.no = no;
this.name = name;
this.nickname = nickname;
}
//为了现实方法,我们重新toString
public override string ToString()
{
return "HeroNode[no=" + no + ",name=" + name + ",nickname=" + nickname + "]";
}
}

五:源码地址

源码地址

数据结构--队列

发表于 2019-11-12 | 分类于 数据结构 | 阅读次数:
字数统计: 1.2k

一:介绍队列

队列是一个有序列表,可以用数组或是链表来实现。

遵循先入先出的原则。即:现存入队列的数据,要先取出。后存入的要后去除

1573570155193

二:数组模拟队列

  • 队列本身就是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图,其中maxSize是该队列的最大容量
  • 因为队列的输出、输入是分布从前后端来处理,因此需要两个变量front及rear分别记录队列前后端的下标,front会随着数据输出而改变,而rear则是随着数据输入二改变
  • rear是队列最后【含】
  • front是队列最前元素【不含】

1573570155193

当我们将数据存入队列时称为“addQueue”,addQueue的处理需要2个步骤:

将尾指针往后移:rear+1,当front==rear[空]

若尾指针rear小于队列的最大下标maxSize-1,则将数据存入rear所指定的数组元素中,否则无法存入数据。rear==maxSize-1[队列满]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public class AarrayQueue
{
//使用数组模拟队列
private int maxSize;//表示数组最大容量
private int front;//指向队列头
private int rear;//队列尾
private int[] arr;//该数组存放数据,模拟队列
//创建队列的构造器
public AarrayQueue(int arrMaxSize)
{
maxSize = arrMaxSize;
arr = new int[maxSize];
front = -1;//指向队列的头部的前一个位置[不包含]
rear = -1;//指向队列的尾部[包含]
}
//判断队列是否满
public bool isFull()
{
return rear == maxSize - 1;
}
//判断队列是否为空
public bool isEmpty()
{
return rear == maxSize;
}
//添加数据到队列
public void addQueue(int n)
{
if (isFull())
{
Console.WriteLine("队列已经满了!");
}
else
{
rear++;
arr[rear] = n;
}
}
//获取数据出队列
public int getQueue()
{
if (isEmpty())
{
throw new Exception("队列已经满了");
}
else
{
return arr[rear--];
}
}
//显示队列的所有数据
public void showQueue()
{
if (isEmpty())
{
Console.WriteLine("当前队列是空的,没有数据!");
}
for (int i = 0; i < maxSize; i++)
{
Console.Write(arr[i]+"\t");
}
}
//显示队列的头数据,
public int headQueue()
{
if (isEmpty())
{
throw new Exception("队列是空的");
}
else
{
return arr[front+1];
}
}
//显示队列的尾数据
public int footQueue()
{
if (isEmpty())
{
throw new Exception("队列是空的");
}
else
{
return arr[rear];
}
}
}

三:数组模拟环形队列

思路:

  • front变量的含义做一个调整:front就指向队列的第一个元素,arr[front]就是队列的第一个元素,front初始值=0。
  • real变量的含义做一个调整:real指向队列的最后一个元素的后一位置,因为希望空出一个空间做为约定,real初始值=0。
  • 当队列满的时候,条件改为 (real+1)%maxSize==front[满]
  • 当队列为空的条件,rear==front[空]
  • 队列的有效的数据的个数(real+maxSize-front)%maxSzie
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace demo2
{
public class CircleArrayQueue
{
//使用数组模拟队列
private int maxSize;//表示数组最大容量
private int front;//front就指向队列的第一个元素
private int rear;//real指向队列的最后一个元素的后一位置
private int[] arr;//该数组存放数据,模拟队列

public CircleArrayQueue(int arrMaxSize)
{
maxSize = arrMaxSize;
arr = new int[maxSize];
}
//判断队列是否满
public bool isFull()
{
return (rear + 1) % maxSize == front;
}
//判断队列是否为空
public bool isEmpty()
{
return rear == front;
}
//添加数据到队列
public void addQueue(int n)
{
if (isFull())
{
Console.WriteLine("队列已经满了!");
}
else
{
arr[rear] = n;
//将readl后移
rear = (rear + 1) % maxSize;
}
}

//获取数据出队列
public int getQueue()
{
if (isEmpty())
{
throw new Exception("队列已经满了");
}
else
{
//这里需要分析出front是指向队列的第一个元素
//1.先把front对应的值保存到一个临时变量
//2.将front后移,考虑取莫
//3.将临时保存的变量返回
int temp = arr[front];
front = (front + 1) % maxSize;
return temp;
}
}
//显示队列的所有数据
public void showQueue()
{
if (isEmpty())
{
Console.WriteLine("当前队列是空的,没有数据!");
}
//思路,从front开始变量,变量多少个元素
for (int i = front; i < front+ size(); i++)
{
Console.Write(arr[i%maxSize] + "\t");
}
Console.WriteLine("");
}
//求当前数组的有效个数
public int size()
{
return (rear + maxSize - front) % maxSize;
}
//显示队列的头数据,
public int headQueue()
{
if (isEmpty())
{
throw new Exception("队列是空的");
}
else
{
return arr[front];
}
}
//显示队列的尾数据
public int footQueue()
{
if (isEmpty())
{
throw new Exception("队列是空的");
}
else
{
return arr[size()-1];
}
}
}
}

四:源码地址

https://github.com/zc282840325/DataStructure/tree/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E9%98%9F%E5%88%97

数据结构-稀疏数组

发表于 2019-11-12 | 分类于 数据结构 | 阅读次数:
字数统计: 603

一:稀疏数组基本介绍

答:当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保护该数组

二:稀疏数组的工作原理:

1573527796664

  1. 记录数组一共有几行几列,有多少个不同的值
  2. 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模

三:应用实例

五子棋游戏:

1573527984103

二维数组转换为稀疏数组:

Row Clo Val
0 11 11 2
1 1 2 1
2 2 3 2
  • 11行11列,有效数据有2个
  • 1行2列,数值为1
  • 2行3列,数值为2

二维数组转换为稀疏数组的思路:

  1. 遍历原始的二维数组,得到有效数据的个数sum
  2. 根据sum就可以创建稀疏数组sparseArr int{sum+1}{3}
  3. 将二维数组的有效数据存入到稀疏数组中

稀疏数组转换为二维数组的思路:

  1. 先读取稀疏数组第一行,根据第一行的数据,创建原始的二维数组
  2. 在读取稀疏书中后几行数据,并赋值原始的二维数组

四:代码实现

//先创建一个原始的二维数据11*11,
//0:表示没有棋子,1表示黑子,2表示白子
int[,] chese = new int[11, 11];
chese[1, 2] = 1;
chese[2, 4] = 2;
chese[7, 8] = 2;
chese[4, 9] = 1;

Console.WriteLine(“原始的二维数组:”);
for (int i = 0; i < Math.Sqrt(chese.Length); i++)
{
for (int j = 0; j < Math.Sqrt(chese.Length); j++)
{
Console.Write(“\t” + chese[i, j]);
}
Console.WriteLine(“”);
}

//第一步获取有效值的数据

int sum = 0;
for (int i = 0; i < Math.Sqrt(chese.Length); i++)
{
for (int j = 0; j < Math.Sqrt(chese.Length); j++)
{
if (chese[i, j] != 0)
{
sum++;
}
}
}

//第二步创建稀疏数组并赋值

//创建稀疏数组
int[,] res = new int[sum + 1, 3];
//给稀疏数组赋值
res[0, 0] = 11;
res[0, 1] = 11;
res[0, 2] = sum;
//遍历二维数组,将非零的值存入稀疏数组
int count = 0;
for (int i = 0; i < Math.Sqrt(chese.Length); i++)
{
for (int j = 0; j < Math.Sqrt(chese.Length); j++)
{
if (chese[i, j] != 0)
{
count++;
res[count, 0] = i;
res[count, 1] = j;
res[count, 2] = chese[i, j];
}
}
}

//打印稀疏数组的值

Console.WriteLine(“稀疏数组:”);
for (int i = 0; i < res.Length / 3; i++)
{
for (int j = 0; j < 3; j++)
{
Console.Write(“\t” + res[i, j]);
}
Console.WriteLine(“”);
}

[]: https://github.com/zc282840325/DataStructure/tree/master/数据结构-稀疏数组 “Github”

数据结构-第一章

发表于 2019-11-12 | 分类于 数据结构 | 阅读次数:
字数统计: 215

一:数据结构有哪些结构?

答:线性结构和非线性结构

1.线性结构:

  • 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系
  • 线性结构有两种 不同的存储结构,即顺序存储结构(数组)和链式存储结构(链表)。顺序存储结构的线性表称为顺序表,顺序表中的存储元素是连续的
  • 链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息
  • 线性结构常见的有:数组、队列、链表和栈。

2.非线性结构:

非线性结构包含:二维数组、多维数组,广义表、树结构、图结构。

三种最常见的问题

发表于 2019-11-11 | 分类于 线程 | 阅读次数:
字数统计: 18

一:CPU过高的问题

二:死锁的问题

三:内存爆满

四大并发类

发表于 2019-11-11 | 分类于 线程 | 阅读次数:
字数统计: 295

一:我们的集合是没有锁机制的

所以我们目前的集合都是线程不安全的

二:有哪些线程安全的集合

  1. ConcurrentQueue==>Queue
  2. ConcurrentDictionary<TKey, TValue> ==>Dictionary
  3. ConcurrentStack==>Stack
  4. ConcurrentBag==>list Or LinkList

三:ConcurrentBag

ConcurrentBag 利用线程槽来分摊Bag中数据

ConcurrentBag 的数据放置咋i多个插入线程的槽位汇总。

底层是链表

 ConcurrentBag<int> vs = new ConcurrentBag<int>();
        vs.Add(1);
        vs.Add(2);
Console.WriteLine(vs.Count);
    for (int i = 0; i < vs.Count; i++)
    {
        var result = 0;
        vs.TryTake(out result);
        Console.WriteLine(result);
    }

四:ConcurrentStack

线程安全的Stack是使用链表实现的,同步版本是用数组实现的

线程安全的Stack是使用Interloacked来实现线程安全,没使用内核锁

ConcurrentStack<int> vs = new ConcurrentStack<int>();
      vs.Push(1);
      vs.Push(2);

      Console.WriteLine(vs.Count);
      for (int i = 0; i < vs.Count; i++)
      {
          var result = 0;
          vs.TryPop(out result);
          Console.WriteLine(result);
      }

五:ConcurrentQueue

使用单链表实现

1
2
3
4
5
6
ConcurrentQueue<int> vs = new ConcurrentQueue<int>();
vs.Enqueue(1);

var result = 0;
vs.TryDequeue(out result);
Console.WriteLine(result);

六:ConcurrentDictionary<TKey, TValue>

1
2
3
4
5
6
7
8
9
ConcurrentDictionary<int, int> dic = new ConcurrentDictionary<int, int>();
dic.TryAdd(1,10);

dic.ContainsKey(1);

foreach (var item in dic)
{
Console.WriteLine(item.Key+item.Value);
}

混合模式锁

发表于 2019-11-11 | 分类于 线程 | 阅读次数:
字数统计: 890

一:混合锁

混合锁=用户模式锁+内核模式锁

1.用户模式锁:

Thread.Sleep(1);让线程休眠毫秒

Thread.Sleep(0);让线程放弃当前的时间片,让本溪拿出更高或者同等线程得到时间片运行

Thread.YieId():让线程立即放弃当前时间片,让更低级别的线程得到运行,当其他thread时间片用完,本thread再度唤醒。

Yield<Sleep(0)<Sleep(1)

一个时间片等于30ms

2.混合锁有哪些:

  1. SemaphoreSlim

  2. ManualResetEventSlim

  3. ReaderWriterLockSlim

  4. ReaderWriterLockSlimWrapper

相比较内核模式,效率更高

1.ManualResetEventSlim:优化点

  • 构造函数中已经可以不提供默认状态,默认是false,表示合围状态

  • 使用wait代替waitOne(waitHandle提供了一个方法)

  • 支持任务取消

       public static ManualResetEventSlim manual = new ManualResetEventSlim(true);
        static void Main(string[] args)
        {
            CancellationTokenSource source = new CancellationTokenSource();
            while (true)
            {
                Console.WriteLine("-----------");
                try
                {
                        manual.Wait(3, source.Token);
                        Console.WriteLine("Hello Wordl!");
                        manual.Set();
                        source.Cancel();      }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                break;
            }
        }
        Console.ReadKey();
    }

    1573438077240

  • wait()中的实现逻辑

    waitone()调用底层的win32函数:

    1
    2
    3
    [SecurityCritical]
    [**MethodImpl**(MethodImplOptions.InternalCall)]
    **private** static extern **int** **WaitOneNative**(SafeHandle waitableSafeHandle, **uint** millisecondsTimeout, **bool** hasThreadAffinity, **bool** exitContext);

    wait():

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    // System.Threading.ManualResetEventSlim
    [__DynamicallyInvokable]
    public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
    {
    this.ThrowIfDisposed();
    cancellationToken.ThrowIfCancellationRequested();
    if (millisecondsTimeout < -1)
    {
    throw new ArgumentOutOfRangeException("millisecondsTimeout");
    }
    if (!this.IsSet)
    {
    if (millisecondsTimeout == 0)
    {
    return false;
    }
    uint startTime = 0u;
    bool flag = false;
    int num = millisecondsTimeout;
    if (millisecondsTimeout != -1)
    {
    startTime = TimeoutHelper.GetTime();
    flag = true;
    }
    int num2 = 10;
    int num3 = 5;
    int num4 = 20;
    int spinCount = this.SpinCount;
    for (int i = 0; i < spinCount; i++)
    {
    if (this.IsSet)
    {
    return true;
    }
    if (i < num2)
    {
    if (i == num2 / 2)
    {
    Thread.Yield();
    }
    else
    {
    Thread.SpinWait(4 << i);
    }
    }
    else
    {
    if (i % num4 == 0)
    {
    Thread.Sleep(1);
    }
    else
    {
    if (i % num3 == 0)
    {
    Thread.Sleep(0);
    }
    else
    {
    Thread.Yield();
    }
    }
    }
    if (i >= 100 && i % 10 == 0)
    {
    cancellationToken.ThrowIfCancellationRequested();
    }
    }
    this.EnsureLockObjectCreated();
    using (cancellationToken.InternalRegisterWithoutEC(ManualResetEventSlim.s_cancellationTokenCallback, this))
    {
    object @lock = this.m_lock;
    lock (@lock)
    {
    while (!this.IsSet)
    {
    cancellationToken.ThrowIfCancellationRequested();
    if (flag)
    {
    num = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout);
    if (num <= 0)
    {
    bool result = false;
    return result;
    }
    }
    this.Waiters++;
    if (this.IsSet)
    {
    int waiters = this.Waiters;
    this.Waiters = waiters - 1;
    bool result = true;
    return result;
    }
    try
    {
    if (!Monitor.Wait(this.m_lock, num))
    {
    bool result = false;
    return result;
    }
    }
    finally
    {
    this.Waiters--;
    }
    }
    }
    }
    return true;
    }
    return true;
    }

2.SemaphoreSlim:信号量

Semaphore的WaitOne或者Release方法的调用大约会耗费1微秒的系统时间,而优化后的SemaphoreSlim则需要大致四分之一微秒。

Semaphore就好像一个栅栏,有一定的容量,当里面的线程数量到达设置的最大值时候,就没有线程可以进去。然后,如果一个线程工作完成以后出来了,那下一个线程就可以进去了。Semaphore的WaitOne或Release等操作分别将自动地递减或者递增信号量的当前计数值。当线程试图对计数值已经为0的信号量执行WaitOne操作时,线程将阻塞直到计数值大于0。在构造Semaphore时,最少需要2个参数。信号量的初始容量和最大的容量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static SemaphoreSlim slim = new SemaphoreSlim(2, 10);
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Task.Run(()=> {
Run();
});
}
//等待2秒后
Thread.Sleep(2000);
slim.Release(10); Console.ReadKey();
}
static void Run()
{
slim.Wait();
Thread.Sleep(1000*5);
Console.WriteLine("当前t1={0}正在运行,时间:{1}",Thread.CurrentThread.ManagedThreadId,DateTime.Now);
slim.Release();
}

3.ReaderWriterLockSlim

用EnterReadLock代替AcquireReaderLock方法,性能比内核版本要高很多。

1
2
3
4
5
public static ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim();
lockSlim.EnterReadLock();
lockSlim.ExitReadLock();
lockSlim.EnterWriteLock();
lockSlim.ExitWriteLock();

混合锁:先在用户模式下内旋,如果超过一定的值,会切换到内核锁。

​ 在内旋的情况下,我们会看到大量的Sleep(0),Sleep(1),Yield等语法

1234…6

张聪

60 日志
6 分类
11 标签
© 2020 张聪
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
总访客 人 总访问量 次